Piping raw []byte video to ffmpeg - Go - go

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)

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.

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

Related

Pipe FFMPEG's output to S3 PutObject

I'm trying to replicate the AWS Lambda solution of the following tutorial with Go AWS SDK. My process is a little bit more complicated though because I want to be able to do the following:
Convert the video to h265
Transpose it to m3u8 (HLS)
Upload the h265 video directly from pipe (without local copy)
Upload the m3u8 and ts files (apparently impossible to do without local copy, not sure though)
I came up with the following:
func upload(video_name string) {
var compress_args = []string{
"-loglevel", "fatal",
"-i", video_name + ".mp4",
"-vcodec", "libx265",
"-x265-params", "log-level=fatal",
"-crf", "28",
"-f", "mp4",
"-movflags", "frag_keyframe+empty_moov",
"-", // pipes output to stdout
}
var transpose_args = []string{
"-loglevel", "fatal",
"-i", "-", // Read input from stdin
"-vcodec", "libx265",
"-x265-params", "log-level=fatal",
"-crf", "28",
"-start_number", "0",
"-hls_time", "1",
"-hls_list_size", "0",
"-f", "hls", "tmp/" + video_name + ".m3u8",
}
cmd := exec.Command("ffmpeg", compress_args...)
cmd2 := exec.Command("tee", "new_" + video_name + ".mp4") // how to improve that ?
cmd3 := exec.Command("ffmpeg", transpose_args...)
cmd.Stderr = os.Stderr
cmd2.Stderr = os.Stderr
cmd3.Stderr = os.Stderr
cmd2.Stdin, _ = cmd.StdoutPipe()
cmd3.Stdin, _ = cmd2.StdoutPipe()
cmd3.Stdout = os.Stdout
cmd.Start()
cmd2.Start()
cmd3.Start()
cmd.Wait()
cmd2.Wait()
cmd3.Wait()
}
For now, what this does is that it pipes the compression, the tee and the transpose commands. The tee creates a local copy of the .mp4 and relay to the compression command. Now, I would like to be able to pass tee's output to the Body parameter for s3.PutObjectInput.
Should I use a Named Pipe, let tee write on it and then somehow pass that to the PutObjectInput? is it even possible? what are the other possibilities ?
Edit
I made the compressed file upload work by using named pipes and not waiting for the commands to finish (original idea). Here is the new code:
func upload(video_name string) {
// ARGS
syscall.Mkfifo("pipe", 0644)
// CMDS and STDOUT/STDIN redirections
named_pipe, _ := os.OpenFile("pipe", os.O_RDONLY|syscall.O_NONBLOCK, 0600)
defer named_pipe.Close()
syscall.SetNonblock(int(named_pipe.Fd()), false)
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(region),
}))
uploader := s3manager.NewUploader(sess, func(u *s3manager.Uploader) {
u.PartSize = 5 * 1024 * 1024 // 5MB
})
compress_cmd.Start()
tee_cmd.Start()
transpose_cmd.Start()
_, err := uploader.UploadWithContext(context.Background(), &s3manager.UploadInput{
Bucket: aws.String(bucket),
Key: aws.String(video_name + ".mp4"),
Body: &reader{named_pipe},
})
}
where reader is:
type reader struct {
r io.Reader
}
func (r *reader) Read(p []byte) (int, error) {
return r.r.Read(p)
}
However, is there a way to wait for the whole process to finish before uploading the HLS output?
I tried waiting for transpose_cmd to finish after the UploadWithContext but this makes the Uploader quit prematurely.

How can I get the file output of the ffmpeg command?

I used the following code to generate a .mp4 file:
args := []string{"-i", "rtsp://zigong.stream.xl02.cn:557/HongTranSvr?DevId=1b038d27-858c-46a1-b803-a2984af343df&Session=1b038d27-858c-46a1-b803-a2984af343df",
"-vcodec", "copy", "-t", "5", "-y", "output.mp4"}
command := exec.Command("ffmpeg", args...)
bytes, err := command.Output()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(bytes)) //result:0
Is there a way to get the []byte format of the output.mp4?
Use the pipe:1 for the ouput instead of a file path. The example below shows you how to access to the FFmpeg command output and the generated file.
var stdOut bytes.Buffer
var stdErr bytes.Buffer
args := []string{"-i", "test.mp4", "-vcodec", "copy", "-t", "5", "-f", "mp4", "pipe:1"}
command := exec.Command("ffmpeg", args...)
command.Stdout = bufio.NewWriter(&stdOut)
command.Stderr = bufio.NewWriter(&stdErr)
if err := command.Run(); err != nil {
log.Println(err)
log.Println(string(stdErr.Bytes()))
return
}
// FFmpeg log
log.Println(string(stdErr.Bytes()))
// data from FFmpeg
log.Println(len(stdOut.Bytes()))
The problem with mp4 is that FFmpeg can not directly output it, because the metadata of the file is written at the beginning of the file but must be written at the end of the encoding. Follow this link for more information: FFMPEG: Transmux mpegts to mp4 gives error: muxer does not support non seekable output

Decoding bmp images from stdout using go image library

I am trying to decode a bmp image using the image and golang.org/x/image/bmp libraries. The image is output by ffmpeg into stdout. This is the code to get the frame:
cmd := exec.Command("ffmpeg", "-accurate_seek", "-ss", strconv.Itoa(index), "-i",
filename, "-frames:v", "1", "-hide_banner", "-loglevel", "0", "pipe:.bmp")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
o := bufio.NewReader(&out)
and then I decode it using img, _, err := image.Decode(o)
However this gives an error of "image: unknown format". I have already registered the bmp format in the main method, and I have successfully decoded actual BMP files from disk previously, just not from stdout.
I have tried just using bmp.Decode instead of image.Decode but this just gives the error "EOF".
I thought maybe I was not getting the stdout in the correct way, but if I just write it straight to a file:
o := bufio.NewReader(&out)
outputfile, err := os.Create("test.bmp")
if err != nil {
log.Fatal(err)
}
defer outputfile.Close()
io.Copy(outputfile, o)
then it works fine and I can open it.
Edit: code
Update: turns out the issue was -ss takes time not frame index.
I tried to reproduce the issue but it seems to work for me. Maybe add cmd.Stderr = os.Stderr to see if ffmpeg output gives come clue. Could you also post a fully runnable example?
package main
import (
"bytes"
"image"
"log"
"os"
"os/exec"
_ "golang.org/x/image/bmp"
)
func main() {
cmd := exec.Command(
"docker", "run",
"--rm",
"mwader/static-ffmpeg",
"-ss", "5",
"-f", "lavfi",
"-i", "testsrc",
"-frames:v", "1",
"-f", "image2",
"-vcodec", "bmp",
"pipe:1",
)
var out bytes.Buffer
cmd.Stderr = os.Stderr
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
img, imgFormat, imgErr := image.Decode(&out)
log.Printf("img.Bounds(): %#+v\n", img.Bounds())
log.Printf("imgFormat: %#+v\n", imgFormat)
log.Printf("imgErr: %#+v\n", imgErr)
}

Golang and ffmpeg realtime streaming input/output

I'm new to Go!
I'm doing a simple test that is reading the output from ffmpeg and writing to a file.
I know I can do it in a different way, simply convert, but this is the beginning of a project where I want to later manipulate the bytes read, changing them and then sending them to the output. And the input will be UDP and output will be UDP too, that is, I will get the ffmpeg output I will treat the bytes as I wish to do and then I will throw these bytes as input into another ffmpeg process which output is UDP as well.
With this simple test the result of the file does not run in VLC, I believe I'm writing the bytes in the output file correctly, but the output file always has 1MB less than the input file.
I would like some help to elucidate what would be the best way to write this test that I am doing, based on that I can get out of the place. I do not know if it's exactly wrong, but I have the impression that it is.
The input file is a video in 4K, h264, I believe the output should be the same, because in this simple test I am simply reading what goes out in the cmd writing in the file.
Follow the code for analysis:
package main
import (
"os/exec"
"os"
)
func verificaErro(e error) {
if e != nil {
panic(e)
}
}
func main() {
dir, _ := os.Getwd()
cmdName := "ffmpeg"
args := []string{
"-hide_banner",
"-re",
"-i",
dir + "\\teste-4k.mp4",
"-preset",
"superfast",
"-c:v",
"h264",
"-crf",
"0",
"-c",
"copy",
"-f", "rawvideo", "-",
}
cmd := exec.Command(cmdName, args...)
stdout, err := cmd.StdoutPipe()
verificaErro(err)
err2 := cmd.Start()
verificaErro(err2)
fileOutput := dir + "/out.raw"
var _, err3 = os.Stat(fileOutput)
if os.IsNotExist(err3) {
var file, err = os.Create(fileOutput)
verificaErro(err)
defer file.Close()
}
f, err4 := os.OpenFile(dir+"/out.raw", os.O_RDWR|os.O_APPEND, 0666)
verificaErro(err4)
bytes := make([]byte, 1024)
for {
_, err5 := stdout.Read(bytes)
if err5 != nil {
continue
}
if len(bytes) > 0 {
_, err6 := f.Write(bytes)
verificaErro(err6)
} else {
break
}
}
f.Close()
}
You must check return values of stdout.Read. Please note that the number of bytes read (nr) may be smaller than the buffer size, so you need to re-slice the buffer to get a valid content. Modify reading loop as follows:
chunk := make([]byte, 40*1024)
for {
nr, err5 := stdout.Read(chunk)
fmt.Printf("Read %d bytes\n", nr)
//do something with the data
//e.g. write to file
if nr > 0 {
validData := chunk[:nr]
nw, err6 := f.Write(validData)
fmt.Printf("Write %d bytes\n", nw)
verificaErro(err6)
}
if err5 != nil {
//Reach end of file (stream), exit from loop
if err5 == io.EOF {
break
}
fmt.Printf("Error = %v\n", err5)
continue
}
}
if err := cmd.Wait(); err != nil {
fmt.Printf("Wait command error: %v\n", err)
}
Another solution is utilizing io.Copy to copy the whole output into golang buffer. The code snippet will look like:
var buf bytes.Buffer
n, err := io.Copy(&buf, stdout)
verificaErro(err)
fmt.Printf("Copied %d bytes\n", n)
err = cmd.Wait()
fmt.Printf("Wait error %v\n", err)
//do something with the data
data := buf.Bytes()
f, err4 := os.OpenFile(dir+"/out.raw", os.O_RDWR|os.O_APPEND, 0666)
verificaErro(err4)
defer f.Close()
nw, err := f.Write(data)
f.Sync()
fmt.Printf("Write size %d bytes\n", nw)

How to pass []byte to external exiftool via stdin?

I am trying to do in golang the bash equivalent:
cat image.jpg | exiftool -author=some_auth - > updated_image.jpg
The exiftool '-' option makes it read from stdin, but say I have the image stored in a variable like
var img []bytes //in golang
I want stdin to contain the bytes from img and the system call to exiftool to read these from stdin, save the result (stdout) in another []byte - I am new to golang, how what do i approach this ?
I don't want to save to temp files on disk.
Thanks
Something like
out, err := os.Create("updated_image.jpg")
if err != nil {
log.Fatal(err)
}
cmd := exec.Command("exiftool", "-author=some_auth", "-")
cmd.Stdout = out
cmd.Stdin = bytes.NewReader(img)
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
out.Close()
should work.
Note that I haven't tested the code.

Resources