I'm extracting a frame from a video using ffmpeg and golang. If I have a video in bytes instead of saved on disk as an .mp4, how do I tell ffmpeg to read from those bytes without having to write the file to disk, as that is much slower?
I have this working reading from a file, but I'm not sure how to read from bytes.
I've looked at the ffmpeg documentation here but only see output examples instead of input examples.
func ExtractImage(fileBytes []byte){
// command line args, path, and command
command = "ffmpeg"
frameExtractionTime := "0:00:05.000"
vframes := "1"
qv := "2"
output := "/home/ubuntu/media/video-to-image/output-" + time.Now().Format(time.Kitchen) + ".jpg"
// TODO: use fileBytes instead of videoPath
// create the command
cmd := exec.Command(command,
"-ss", frameExtractionTime,
"-i", videoPath,
"-vframes", vframes,
"-q:v", qv,
output)
// run the command and don't wait for it to finish. waiting exec is run
// ignore errors for examples-sake
_ = cmd.Start()
_ = cmd.Wait()
}
You can make ffmpeg to read data from stdin rather than reading file from disk, by specifying - as the value of the option -i. Then just pass your video bytes as stdin to the command.
func ExtractImage(fileBytes []byte){
// command line args, path, and command
command := "ffmpeg"
frameExtractionTime := "0:00:05.000"
vframes := "1"
qv := "2"
output := "/home/ubuntu/media/video-to-image/output-" + time.Now().Format(time.Kitchen) + ".jpg"
cmd := exec.Command(command,
"-ss", frameExtractionTime,
"-i", "-", // to read from stdin
"-vframes", vframes,
"-q:v", qv,
output)
cmd.Stdin = bytes.NewBuffer(fileBytes)
// run the command and don't wait for it to finish. waiting exec is run
// ignore errors for examples-sake
_ = cmd.Start()
_ = cmd.Wait()
}
You may need to run ffmpeg -protocols to determine if the pipe protocol (to read from stdin) is supported in your build of ffmpeg.
Related
I am trying to get a list of files from the fd (find) utility and pass them to fzf while simultaneously saving that list to a text file on the hard disk. In the following code, everything is passed from fd to fzf just fine, but not all of the results make it to the text file. Why is this? The number of results that make it to the text file varies each time I run the code (the total number of lines output by fd is about 1200, but the text file will only ever receive between 200 to 900 of those lines).
fdFile, _ := os.OpenFile("./fd-output.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
defer fdFile.Close()
find := exec.Command("fd", ".", "/etc")
fzf := exec.Command("fzf")
fzf.Stderr = os.Stderr
r, w := io.Pipe()
find.Stdout = w
fzf.Stdin = r
var fzfSelection bytes.Buffer
fzf.Stdout = &fzfSelection
find.Start()
go func() {
outs := []io.Writer{fdFile, w}
io.Copy(io.MultiWriter(outs...), r)
}()
fzf.Start()
find.Wait()
w.Close()
fzf.Wait()
fmt.Print(fzfSelection.String())
I read somewhere that my issue sounds like some of the data is not being flushed to the file. However, I have tried appending .Flush() to every reader/writer in the above code and always get an error that tells me the method does not exist.
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.
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
This question already has an answer here:
How can I redirect the stdout and stderr of a command to both the console and a log file while outputting in real time?
(1 answer)
Closed 5 years ago.
I have a small Go tool which basically allows the user to define an command that than will be run using os/exec.
My problem is that I want to show the user the output (stdout/stderr) of the command.
An example could look like this:
The user defines a command that in the end is sh test.sh.
Content of test.sh:
echo "Start"
sleep 7s
echo "Done"
With my current implementation the user can only see the output once the complete command finished. In the example above the user wouldn't see the output Start until the sleep command and the second echo finish.
I currently retrieve the output of the command like this:
cmd := exec.Command(command, args...)
cmd.Dir = dir
// Attach to the standard out to read what the command might print
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Panic(err)
}
// Execute the command
if err := cmd.Start(); err != nil {
log.Panic(err)
}
buf := new(bytes.Buffer)
buf.ReadFrom(stdout)
log.Print(buf.String())
Is it somehow possible to read the stdout/stderr in real-time. Meaning that as soon as the user defined command creates and output it is printed?
Thank you mh-cbon. That pushed me in the right direction.
The code now looks like this and does exactly what I want it to do. I also found that when I use Run() instead of Start() the execution of the program only continues after the command has finished.
cmd := exec.Command(command, args...)
cmd.Dir = dir
var stdBuffer bytes.Buffer
mw := io.MultiWriter(os.Stdout, &stdBuffer)
cmd.Stdout = mw
cmd.Stderr = mw
// Execute the command
if err := cmd.Run(); err != nil {
log.Panic(err)
}
log.Println(stdBuffer.String())
Im trying the following, to use go to bundle a folder of html files using the CMD web2exe.
cmd := exec.Command("web2exe-win.exe", "html-folder --main index.html --export- to windows-x32 --output-dir")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
fmt.Println(err)
}
fmt.Println(out)
When a program exits non-zero it means that it could not run successfully and typically it has written an error message to STDERR (or STDOUT). You should somehow capture or print the output streams so you can inspect them for error messages. For example:
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Note also that your command line arguments should be separate array elements (instead of space separated elements in a single string as they are now):
cmd := exec.Command("web2exe-win.exe", "html-folder", "--main", "index.html", "--export-to", "windows-x32", "--output-dir")