11

I have a video directly from the http body in a [] byte format:

//Parsing video
videoData, err := ioutil.ReadAll(r.Body)
if err != nil {
    w.WriteHeader(UPLOAD_ERROR)
    w.Write([]byte("Error uploading the file"))
    return
}

and I need a single frame of the video and convert it to a png. This is how someone would do it with a static and encoded file using ffmpeg:

        filename := "test.mp4"
        width := 640
        height := 360
        cmd := exec.Command("ffmpeg", "-i", filename, "-vframes", "1", "-s", fmt.Sprintf("%dx%d", width, height), "-f", "singlejpeg", "-")
        var buffer bytes.Buffer
        cmd.Stdout = &buffer
        if cmd.Run() != nil {
            panic("could not generate frame")
        }

How can I achieve the same with a raw video?

A user from reddit told me that I might achieve this with https://ffmpeg.org/ffmpeg-protocols.html#pipe but I was unable to find any resources.

Any help is appreciated, thanks.

(EDIT: I tried to pipe the []byte array to ffmpeg now, but ffmpeg does not fill in my buffer:

width := 640
height := 360
log.Print("Size of the video: ", len(videoData))


cmd := exec.Command("ffmpeg", "-i", "pipe:0", "-vframes", "1", "-s", fmt.Sprintf("%dx%d", width, height), "-f", "singlejpeg", "-")
cmd.Stdin = bytes.NewReader(videoData)

var imageBuffer bytes.Buffer
cmd.Stdout = &imageBuffer
err := cmd.Run()

if err != nil {
    log.Panic("ERROR")
}

imageBytes := imageBuffer.Bytes()
log.Print("Size of the image: ", len(imageBytes))

But I get following error:

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7ff05d002600]stream 0, offset 0x5ded: partial file

pipe:0: Invalid data found when processing input

Finishing stream 0:0 without any data written to it.

frame= 0 fps=0.0 q=0.0 Lsize= 0kB time=-577014:32:22.77 bitrate= -0.0kbits/s speed=N/A video:0kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

Output file is empty, nothing was encoded (check -ss / -t / -frames parameters if used)

Community
  • 1
  • 1
thelearner
  • 1,440
  • 3
  • 27
  • 58
  • 3
    `cmd.Stdin = bytes.NewReader(videoData)`, and replace filename with "pipe:0" – Peter Apr 12 '18 at 15:47
  • This is a duplicate of your [previous question](https://stackoverflow.com/questions/49798803/creating-an-image-from-a-video-frame), being a little more specific. – icza Apr 12 '18 at 15:49
  • @peter Thanks a lot. The video has 50MB. Is there a way to do it more efficient? Like only pushing those bytes that are required for the frame? – thelearner Apr 12 '18 at 16:43
  • 1
    @icza No it's not. In this question I am specifically asking on how to push bytes to ffmpeg. Those two questions might be related, but the foundation is different. – thelearner Apr 12 '18 at 16:45
  • 1
    To know how many bytes you need requires you to decoce the video, at which point you probably don't need ffmpeg anymore. But I would expect ffmpeg to stop reading after the first frame. You can tell how much ffmpeg reads by using an [io.TeeReader](https://golang.org/pkg/io/#TeeReader). If it turns out that ffmpeg reads everything, an [io.LimitReader](https://golang.org/pkg/io/#LimitReader) might help. – Peter Apr 12 '18 at 17:10
  • 3
    @Peter Or instead of `io.LimitReader` just slice the input data, e.g. `bytes.NewReader(videoData[:limit])` (but don't forget to check slice length to avoid runtime panic). – icza Apr 12 '18 at 17:24
  • I tried what you suggested me, but ffmpeg doesn't fill in my buffer. – thelearner Apr 14 '18 at 10:07

2 Answers2

3

I need a single frame of the video and convert it to a png. This is how someone would do it with ffmpeg.

There is a popular go library that is exactly made for what you search for: https://github.com/bakape/thumbnailer

thumbnailDimensions := thumbnailer.Dims{Width: 320, Height: 130}

thumbnailOptions := thumbnailer.Options{JPEGQuality:100, MaxSourceDims:thumbnailer.Dims{}, ThumbDims:thumbnailDimensions, AcceptedMimeTypes: nil}

sourceData, thumbnail, err := thumbnailer.ProcessBuffer(videoData, thumbnailOptions)

imageBytes := thumbnail.Image.Data

They use ffmpeg under the hood, but removes the abstraction for you.

thelearner
  • 1,440
  • 3
  • 27
  • 58
0

Please check this out, I wrote this to down sample mp3 files to 128k bitrate and it should work with you. Please change the command which suits you:

package main

import (
    "bytes"
    "io/ioutil"
    "log"
    "os"
    "os/exec"
)

func check(err error) {
    if err != nil {
        log.Fatalln(err)
    }
}

func main() {
    file, err := os.Open("test.mp3") // open file
    check(err)
    
    defer file.Close()
    buf, err := ioutil.ReadAll(file)
    check(err)

    cmd := exec.Command("ffmpeg", "-y", // Yes to all
        //"-hide_banner", "-loglevel", "panic", // Hide all logs
        "-i", "pipe:0", // take stdin as input
        "-map_metadata", "-1", // strip out all (mostly) metadata
        "-c:a", "libmp3lame", // use mp3 lame codec
        "-vsync", "2", // suppress "Frame rate very high for a muxer not efficiently supporting it"
        "-b:a", "128k", // Down sample audio birate to 128k
        "-f", "mp3", // using mp3 muxer (IMPORTANT, output data to pipe require manual muxer selecting)
        "pipe:1", // output to stdout
    )

    resultBuffer := bytes.NewBuffer(make([]byte, 5*1024*1024)) // pre allocate 5MiB buffer

    cmd.Stderr = os.Stderr // bind log stream to stderr
    cmd.Stdout = resultBuffer // stdout result will be written here

    stdin, err := cmd.StdinPipe() // Open stdin pipe
    check(err)

    err = cmd.Start() // Start a process on another goroutine
    check(err)

    _, err = stdin.Write(buf) // pump audio data to stdin pipe
    check(err)

    err = stdin.Close() // close the stdin, or ffmpeg will wait forever
    check(err)

    err = cmd.Wait() // wait until ffmpeg finish
    check(err)

    outputFile, err := os.Create("out.mp3") // create new file
    check(err)
    defer outputFile.Close()
    
    _, err = outputFile.Write(resultBuffer.Bytes()) // write result buffer to file
    check(err)
}

Reference: https://gist.github.com/aperture147/ad0f5b965912537d03b0e851bb95bd38