我试图在golang中用os / exec调用shell命令,该命令将花费一些时间,因此我想检索reatime输出并打印处理后的输出(进度比率编号)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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命令输出如下:
1 2 3 4 | 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命令输出的最后一行,它不断变化,我想将变化打印出来,就像
1 2 3 4 | 18% 44% 69% 100% |
我怎样才能做到这一点?
-
fmt 库具有一组扫描功能,可以将格式化的字符串解析为值。 您可以在标准输出管道上使用"扫描仪"来扫描行并扫描每条格式化的行。 您需要弄清楚如何从ffmpeg中获得完整与总计的比率。 - 流命令输出进度可能重复
- @icza,您能看看我发布的答案吗,为什么我的屏幕上什么都没打印出来?
- @seaguest我也在这里添加了答案。
看起来ffmpeg发送所有诊断消息("控制台输出")
到stderr而不是stdout。下面的代码对我有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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的版本如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 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) } } |
我确实发现他在那篇文章中提到的icza解决方案非常有用,但是并不能解决我的问题。
我做了如下测试:
1,我写了一个脚本,每秒打印一些信息十次,这是script.sh
1 2 3 4 5 6 7 | #!/bin/bash for i in {1..10} do echo"step" $i sleep 1s done |
2,读取标准输出并从标准输出中提取所需的信息,并执行一些过程以获取所需的格式,这是代码:
包主
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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 ", progressingRatio) } } |
这确实适用于我的script.sh测试,但对于ffmpeg命令却不起作用,在ffmpeg的情况下,什么都没有打印,并且进程完成了(不卡住),我想将数据写入ffmpeg的stdout的方式是有点特殊(也许根本没有换行符,我尝试了icza的解决方案,但仍然不起作用)。
- 您可以在伪终端中运行ffmpeg,这可能会起作用(?)
当您具有从Go开始的外部命令的
某种方式意味着您可以将数据发送到其标准输入,并且可以读取其标准输出和错误流。
压力是标准的。如果外部进程正在通过网络连接发送数据,或者正在将数据写入文件,则将无法通过上述3个流截取这些数据。
现在进入
因此,您看不到
通常,如果要捕获此类应用程序的输出,则需要一个能够捕获终端窗口(文本)内容的库。在较容易的情况下,该应用程序支持将这些输出转储到通常由额外的命令行参数控制的文件中,然后您可以从Go中读取/监视这些文件。
- "您需要一个能够捕获终端窗口(文本)内容的库">您对此有何建议? 这是朝着需要PTY的方向发展吗?
- @DuncanJones不,我没有任何想法。 我以为像termbox-go这样的库可以做到这一点,但似乎它可以从其缓冲区中返回字符。
- 谢谢。 在此期间,Ive找到了github.com/creack/pty,这做得很好。