V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
LeeReamond
V2EX  ›  问与答

如何用 Python 实时获取命令行程序的输出?

  •  
  •   LeeReamond · 2020 年 2 月 29 日 · 1843 次点击
    这是一个创建于 2234 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题,想要写一个 gui 程序,捕获 x265 编码器的输出结果,实时显示出来。

    目前查到了一个捕获 subprocess 输出流的范例程序,大概像这样:

    import subprocess
    
    cmd = r'ping www.baidu.com -n 4'
    popen = subprocess.Popen(cmd, stdout = subprocess.PIPE ,stderr=subprocess.STDOUT ,shell=True)
    while True:
        next_line = popen.stdout.readline()
        if next_line == b'' and popen.poll() != None:
            break
        else:
            print(next_line.decode('gbk').replace('\r\n','\n') , end='')
    

    当使用 ping 做测试的时候捕获是一切正常的,但是换成编码器后则出了问题 无论使用 x265,或者 ffmpeg 包含的 libx265,问题有两个,

    1、使用这些编码器时,输出的其中一部分是推流到 stderr 流,而另一些则不是,比如显示实时监控状态的输出信息。这使我获取不到实时编码进度。

    再表述详细一点

    比如如果我将上文中的 cmd 的内容替换成 r"x265 --y4m --crf 21 --output output.hevc input.y4m"这句的话。(假设输入一个 100 帧的测试片段)

    那么正常情况下,如果在 powershell 里操作,x265 应该按时间顺序显示以下这些行

    y4m  [info]: 1920x1080 fps 24000/1001 i420p10 frames 0 - 100 of 101
    x265 [info]: Using preset ultrafast & tune none
    raw  [info]: output file: C:\temp\output.hevc
    x265 [info]: Main 10 profile, Level-4 (Main tier)
    x265 [info]: Thread pool created using 16 threads
    x265 [info]: Slices                              : 1
    x265 [info]: frame threads / pool features       : 4 / wpp(34 rows)
    x265 [info]: Coding QT: max CU size, min CU size : 32 / 16
    x265 [info]: Residual QT: max TU size, max depth : 32 / 1 inter / 1 intra
    x265 [info]: ME / range / subpel / merge         : dia / 57 / 0 / 2
    x265 [info]: Keyframe min / max / scenecut / bias: 23 / 250 / 0 / 5.00
    x265 [info]: Lookahead / bframes / badapt        : 5 / 3 / 0
    x265 [info]: AQ: mode / str / qg-size / cu-tree  : 1 / 0.0 / 32 / 1
    x265 [info]: Rate Control / qCompress            : CRF-21.0 / 0.60
    x265 [info]: tools: strong-intra-smoothing lslices=6 deblock
    
    [1.0%] 1/101 frames, 6.289 fps, 7217.8 kb/s
    [25.7%] 26/101 frames, 59.23 fps, 299.23 kb/s
    [45.5%] 46/101 frames, 66.76 fps, 322.81 kb/s  
    [69.3%] 70/101 frames, 73.30 fps, 224.53 kb/s
    [93.1%] 94/101 frames, 77.05 fps, 173.67 kb/s   
    
    x265 [info]: frame I:      1, Avg QP:23.45  kb/s: 7098.44 
    x265 [info]: frame P:     25, Avg QP:25.71  kb/s: 311.24  
    x265 [info]: frame B:     75, Avg QP:28.33  kb/s: 23.89   
    x265 [info]: consecutive B-frames: 3.8% 0.0% 0.0% 96.2% 
    
    encoded 101 frames in 1.22s (82.58 fps), 165.06 kb/s, Avg QP:27.64
    

    但实际执行程序的表现是,程序的开头和结尾部分,正确按照 python 脚本的期望执行,每输出一行,则popen.stdout.readline() 成功获取一行、输出、重新执行下一轮 while 循环,

    而程序中间输出进度的那几行却不能按照期望实时推流显示

    [1.0%] 1/101 frames, 6.289 fps, 7217.8 kb/s
    [25.7%] 26/101 frames, 59.23 fps, 299.23 kb/s
    [45.5%] 46/101 frames, 66.76 fps, 322.81 kb/s  
    [69.3%] 70/101 frames, 73.30 fps, 224.53 kb/s
    [93.1%] 94/101 frames, 77.05 fps, 173.67 kb/s 
    

    ↑ 也就是这几行 他们的实际表现是,会在进度达到 100%后统一推流到 stdout,然后被readline()获取,在未达到 100%之前 readline()是阻塞的。

    但是这样一来就没有意义了啊,我写 gui 的其中一个需求就是想要更人性化的进度显示,所以实际上获取的重点反而是希望实时显示这些进度。 不知道哪位大佬能解惑一下发生了什么,无法理解这种奇怪的表现。

    2、另外一个小郁闷是无论 ffmpeg 还是 x265,与 ping 不同的是他们输出的信息都是输出到 stderr 流的,这个很奇怪,我想知道为什么他们都不统一。如果我希望新增很多组件,难道还要手动每一个检查他们是输出到 stdout 流还是 stderr 流吗。

    delectate
        1
    delectate  
       2020 年 2 月 29 日
    StackOverflow is your savior:

    How can I capture real time command line output of x265.exe with Python?
    https://stackoverflow.com/questions/60462281/how-can-i-capture-real-time-command-line-output-of-x265-exe-with-python


    import re
    import subprocess

    cmd = r'ping www.baidu.com -n 4'
    popen = subprocess.Popen(cmd, stdout = subprocess.PIPE ,stderr=subprocess.STDOUT ,shell=True)

    read_chunk_size = 20
    next_lines = ['']
    while True:
    # read the stdout upto read_chunk_size bytes
    chunk = popen.stdout.read(read_chunk_size)
    # checking if process has finished
    if not chunk and popen.poll() != None:
    break
    # splitting the read string of charachters by either \n, \r or \n\r
    split_chunk = re.split('\n|\r|\n\r', chunk.decode('utf-8'))
    # combining the first of the split strings to the last of the strings
    # which were read in the last iteration. since we are reading upto only
    # a fixed length, we may get incomplete lines
    next_lines = [next_lines[-1] + split_chunk[0]] + split_chunk[1:]
    # iterate upto the second last item in next_lines, since the last item may
    # still be incomplete which will be read in the next iteration
    for line in next_lines[:-1]:
    # read your lines here
    print(line)
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   2915 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 07:01 · PVG 15:01 · LAX 00:01 · JFK 03:01
    ♥ Do have faith in what you're doing.