Search code examples
pythonpython-3.xsubprocessctypes7zip

Tracking 7zip progress on Windows 10 with Python


I understand 7zip has some issue, where it masks its progress from code that tries to call it (not sure why).

I saw here that -bsp1 flag should show the hidden progress, but still nothing in Python:

from subprocess import Popen, PIPE
from time import sleep

cmd = Popen('7z.exe e D:\stuff.rar -od:\stuff -aoa -bsp1'.split(), stdout=PIPE, stderr=PIPE)

while cmd.poll() !=0:  # Not sure this helps anything
    out = cmd.stdout.read()
    print(out)
    sleep(1)

Running the 7z command in the command line gives me a nice percentage until unpacking is done.

In Python, I get 7z's prelude printout (Path, Type etc.) and after that just b'' until I press Ctrl-c

How does 7z know I'm calling it not from the "real" terminal? Can I somehow make it look like I am, maybe using ctypes and some windows kernel call / API?

I saw the term "pseudo terminal" mentioned in regards to this, but I'm not sure it's relevant, and if it is, Windows' ConPTY API is hidden


Solution

  • There is no need to use pseudo-terminal. I am working on windows 10.

    Get the output could be easy but it is hard to get the progress immediately if you use stdout.readline() directly.(Because it contains \r and it will put the cursor in start of the line, then 7zip use space to fill them.).But readline() use \r\n as the seperator.

    In my example, I use stdout.read(1) to get the output directly. Due to the progress line is 12.So I use a number to check it.

    import subprocess
    
    s = "D:/7-Zip/7z.exe e E:/work/Compile/python/python_project/temp/test.zip -oE:/work/Compile/python/python_project/temp/test -aoa -bsp1"
    p = subprocess.Popen(s.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    i = 0
    while True:
        line = p.stdout.readline()
        if line:
            if i == 11:
                s = b""
                while True:
                    char = p.stdout.read(1)
                    if char == b"E": # "Everything is ok" means end
                        break
                    s += char
                    if char == b"%":
                        print(s.decode("gbk"))
                        s = b""
            print(line.decode("gbk"))
            i += 1
    

    This give me: enter image description here


    You could improve it:

    The condition of end.In my code, I used if char == b"E".I don't think it is good.Also if you remove the .decode("gbk") in each print line, you will see the file name and the number,like:

    enter image description here

    Though the char split is different from the cmd(Normally it should be x% xx - filename)So there is one line delay:

    enter image description here