我试图在golang中使用os / exec调用shell命令,该命令需要一些时间,所以我想检索重新计算输出并打印处理后的输出(进度比率数字)。
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strings"
)
func main() {
cmdName := "ffmpeg -i t.webm -acodec aac -vcodec libx264 cmd1.mp4"
cmdArgs := strings.Fields(cmdName)
cmd := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...)
stdout, _ := cmd.StdoutPipe()
cmd.Start()
go print(stdout)
cmd.Wait()
}
// to print the processed information when stdout gets a new line
func print(stdout io.ReadCloser) {
r := bufio.NewReader(stdout)
line, _, err := r.ReadLine()
fmt.Println("line: %s err %s", line, err)
}
我希望有一个功能,可以在命令打印时更新屏幕,
ffmpeg命令输出如下:
frame= 101 fps=0.0 q=28.0 size= 91kB time=00:00:04.13 bitrate= 181.2kbits/
frame= 169 fps=168 q=28.0 size= 227kB time=00:00:06.82 bitrate= 272.6kbits/
frame= 231 fps=153 q=28.0 size= 348kB time=00:00:09.31 bitrate= 306.3kbits/
frame= 282 fps=140 q=28.0 size= 499kB time=00:00:11.33 bitrate= 360.8kbits/
实际上,上面的4行是ffmpeg命令输出的最后一行,它不断变化,我想打印出那个更改,比如
18%
44%
69%
100%
我怎么能实现这个目标?
答案 0 :(得分:7)
看起来ffmpeg发送所有诊断消息("控制台输出") 到 stderr 而不是stdout。下面的代码适合我。
package main
import (
"bufio"
"fmt"
"os/exec"
"strings"
)
func main() {
args := "-i test.mp4 -acodec copy -vcodec copy -f flv rtmp://aaa/bbb"
cmd := exec.Command("ffmpeg", strings.Split(args, " ")...)
stderr, _ := cmd.StderrPipe()
cmd.Start()
scanner := bufio.NewScanner(stderr)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
m := scanner.Text()
fmt.Println(m)
}
cmd.Wait()
}
ffmpeg的版本详述如下。
ffmpeg version 3.0.2 Copyright (c) 2000-2016 the FFmpeg developers
built with Apple LLVM version 7.3.0 (clang-703.0.29)
configuration: --prefix=/usr/local/Cellar/ffmpeg/3.0.2 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-opencl --enable-libx264 --enable-libmp3lame --enable-libxvid --enable-vda
libavutil 55. 17.103 / 55. 17.103
libavcodec 57. 24.102 / 57. 24.102
libavformat 57. 25.100 / 57. 25.100
libavdevice 57. 0.101 / 57. 0.101
libavfilter 6. 31.100 / 6. 31.100
libavresample 3. 0. 0 / 3. 0. 0
libswscale 4. 0.100 / 4. 0.100
libswresample 2. 0.101 / 2. 0.101
libpostproc 54. 0.100 / 54. 0.100
答案 1 :(得分:2)
我确实发现他在那篇文章中提到的icza解决方案非常有用,但它无法解决我的问题。
我做了一点测试如下:
1,我写了一个脚本,每秒打印一些信息十次,这是script.sh
#!/bin/bash
for i in {1..10}
do
echo "step " $i
sleep 1s
done
2,读取stdout并从stdout中提取所需信息并执行一些过程以获得预期的格式,这里是代码: 包主要
import (
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
)
func getRatio(text string) float32 {
re1, _ := regexp.Compile(`step[\s]+(\d+)`)
result := re1.FindStringSubmatch(text)
val, _ := strconv.Atoi(result[1])
return float32(val) / 10
}
func main() {
cmdName := "ffmpeg -i t.webm -acodec aac -vcodec libx264 cmd1.mp4"
//cmdName := "bash ./script.sh"
cmdArgs := strings.Fields(cmdName)
cmd := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...)
stdout, _ := cmd.StdoutPipe()
cmd.Start()
oneByte := make([]byte, 10)
for {
_, err := stdout.Read(oneByte)
if err != nil {
break
}
progressingRatio := getRatio(string(oneByte))
fmt.Printf("progressing ratio %v \n", progressingRatio)
}
}
这对我的script.sh测试有效,但对于ffmpeg命令它不起作用,在ffmpeg的情况下,没有任何内容被打印并且进程完成(没有卡住),我想将数据写入stdout的方式对于ffmpeg来说有点特别(可能根本没有换行符,我尝试过icza的解决方案,但它仍然不起作用)。
答案 2 :(得分:1)
如果您从Go开始拥有exec.Cmd
外部命令的值,则可以使用其Cmd.Stdin
,Cmd.Stdout
和Cmd.Stderr
字段与一些方式。
某些方式意味着您可以将数据发送到标准输入,并且您可以读取其标准输出和错误流。
压力在标准上。如果外部进程正在网络连接上发送数据,或者正在将数据写入文件,则无法通过上述3个流拦截这些数据。
现在开始ffmpeg
。 ffmpeg
和许多其他控制台应用程序不会将数据写入标准输出/错误,但它们使用系统调用或其他库(使用系统调用)来操作终端窗口。当然,应用程序可能会将某些数据发送到标准输出/错误,并可能通过操作终端窗口显示其他数据。
因此您没有看到ffmpeg
的输出,因为您尝试读取其标准输出/错误,但ffmpeg
不会通过写入这些流来显示其输出。
在一般情况下,如果要捕获此类应用程序的输出,则需要一个能够捕获终端窗口(文本)内容的库。在更简单的情况下,应用程序支持将这些输出转储到通常由额外命令行参数控制的文件,然后您可以从Go读取/监视。
答案 3 :(得分:1)
检查以下内容,需要增强功能(不建议按原样使用)但工作正常:)
package main
import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
)
var duration = 0
var allRes = ""
var lastPer = -1
func durToSec(dur string) (sec int) {
durAry := strings.Split(dur, ":")
if len(durAry) != 3 {
return
}
hr, _ := strconv.Atoi(durAry[0])
sec = hr * (60 * 60)
min, _ := strconv.Atoi(durAry[1])
sec += min * (60)
second, _ := strconv.Atoi(durAry[2])
sec += second
return
}
func getRatio(res string) {
i := strings.Index(res, "Duration")
if i >= 0 {
dur := res[i+10:]
if len(dur) > 8 {
dur = dur[0:8]
duration = durToSec(dur)
fmt.Println("duration:", duration)
allRes = ""
}
}
if duration == 0 {
return
}
i = strings.Index(res, "time=")
if i >= 0 {
time := res[i+5:]
if len(time) > 8 {
time = time[0:8]
sec := durToSec(time)
per := (sec * 100) / duration
if lastPer != per {
lastPer = per
fmt.Println("Percentage:", per)
}
allRes = ""
}
}
}
func main() {
os.Remove("cmd1.mp4")
cmdName := "ffmpeg -i 1.mp4 -acodec aac -vcodec libx264 cmd1.mp4 2>&1"
cmd := exec.Command("sh", "-c", cmdName)
stdout, _ := cmd.StdoutPipe()
cmd.Start()
oneByte := make([]byte, 8)
for {
_, err := stdout.Read(oneByte)
if err != nil {
fmt.Printf(err.Error())
break
}
allRes += string(oneByte)
getRatio(allRes)
}
}