简体   繁体   English

将subprocess.call的输出输出到进度条

[英]Piping output of subprocess.call to progress bar

I'm using growisofs to burn an iso through my Python application. 我正在使用growisofs通过我的Python应用程序来刻录iso。 I have two classes in two different files; 我在两个不同的文件中有两个类; GUI() (main.py) and Boxblaze() (core.py). GUI()(main.py)和Boxblaze()(core.py)。 GUI() builds the window and handles all the events and stuff, and Boxblaze() has all the methods that GUI() calls. GUI()构建窗口并处理所有事件和东西,Boxblaze()具有GUI()调用的所有方法。

Now when the user has selected the device to burn with, and the file to be burned, I need to call a method that calls the following command:` 现在当用户选择要刻录的设备和要刻录的文件时,我需要调用一个调用以下命令的方法:

growisofs -use-the-force-luke=dao -use-the-force-luke=break:1913760 -dvd-compat -speed=2 -Z /burner/device=/full/path/to.iso

This command should give an output similar to this: 此命令应该提供类似于此的输出:

Executing 'builtin_dd if=/home/nevon/games/Xbox 360 isos/The Godfather 2/alls-tgod2.iso of=/dev/scd0 obs=32k seek=0'
/dev/scd0: "Current Write Speed" is 2.5x1352KBps.
#more of the lines below, indicating progress.
7798128640/7835492352 (99.5%) @3.8x, remaining 0:06 RBU 100.0% UBU  99.8%
7815495680/7835492352 (99.7%) @3.8x, remaining 0:03 RBU  59.7% UBU  99.8%
7832862720/7835492352 (100.0%) @3.8x, remaining 0:00 RBU   7.9% UBU  99.8%
builtin_dd: 3825936*2KB out @ average 3.9x1352KBps
/dev/burner: flushing cache
/dev/burner: closing track
/dev/burner: closing disc

This command is run in a method called burn() in Boxblaze(). 此命令在Boxblaze()中名为burn()的方法中运行。 It looks simply like this: 它看起来像这样:

def burn(self, file, device):
    subprocess.call(["growisofs", '-dry-run', "-use-the-force-luke=dao", "-use-the-force-luke=break:1913760", "-dvd-compat", "-speed=2", "-Z",  device +'='+ file])

Now my questions are the following: 现在我的问题如下:

  1. How can I get the progress from the output (the percentage in brackets) and have my progress bar be set to "follow" that progress? 如何从输出中获得进度(括号中的百分比)并将我的进度条设置为“跟随”该进度? My progress bar is called in the GUI() class, as such: 我的进度条在GUI()类中调用,如下:

    get = builder.get_object get = builder.get_object

    self.progress_window = get("progressWindow") self.progress_window = get(“progressWindow”)

    self.progressbar = get("progressbar") self.progressbar = get(“progressbar”)

  2. Do I have to run this command in a separate thread in order for the GUI to remain responsive (so that I can update the progress bar and allow the user to cancel the burn if they want to)? 我是否必须在单独的线程中运行此命令才能使GUI保持响应(以便我可以更新进度条并允许用户取消刻录,如果他们想要)? If so, how can I do that and still be able to pass the progress to the progress bar? 如果是这样,我怎么能这样做,仍然能够将进度传递到进度条?


The full code is available on Launchpad if you are interested. 如果您有兴趣,可以在Launchpad上找到完整的代码。 If you have bazaar installed, just run: 如果您安装了bazaar,只需运行:

bzr branch lp:boxblaze

Oh, and in case you were wondering, this application is only meant to work in Linux - so don't worry about cross-platform compatibility. 哦,如果你想知道,这个应用程序只适用于Linux - 所以不要担心跨平台兼容性。

You can use glib.io_add_watch() to watch for output on the pipes connected to stdout and stderr in the subprocess object. 您可以使用glib.io_add_watch()来监视连接到子进程对象中的stdout和stderr的管道上的输出。

proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout_id = glib.io_add_watch(proc.stdout, glib.IO_IN|glib.IO_HUP, stdout_cb)
stderr_id = glib.io_add_watch(proc.stderr, glib.IO_IN|glib.IO_HUP, stderr_cb)

Then when the callback is called it should check for the condition and reads all the data from the pipe and processes it to get the info to update the ProgressBar. 然后,当调用回调时,它应检查条件并从管道读取所有数据并对其进行处理以获取更新ProgressBar的信息。 If the app buffers io then you may have to use a pty to fool it into thinking it's connected to a terminal so it will output a line at a time. 如果app缓冲了io那么你可能不得不使用pty来欺骗它认为它连接到终端所以它会一次输出一行。

To get the output you need to use the subprocess.Popen call. 要获得输出,您需要使用subprocess.Popen调用。 ( stdout = subprocess.PIPE ) stdout = subprocess.PIPE

(Second Question) (第二个问题)

You probably need a separate thread, unless the GUI framework can select on a filedescriptor in the normal loop. 您可能需要一个单独的线程,除非GUI框架可以在正常循环中的filedescriptor上进行选择。

You can have a background thread read the pipe, process it (to extract the progress), the pass that to the GUI thread. 您可以让后台线程读取管道,处理它(以提取进度),将其传递给GUI线程。

## You might have to redirect stderr instead/as well
proc = sucprocess.Popen(command,stdout=subprocess.PIPE)
for line in proc.stdout.readlines():
    ## might not work - not sure about reading lines
    ## Parse the line to extract progress
    ## Pass progress to GUI thread

Edit: 编辑:

I'm afraid I don't want to waste lots of CDs testing it out, so I haven't run it, but by you're comment it looks like it's not outputing the info to stdout, but to stderr. 我担心我不想浪费大量的CD测试它,所以我没有运行它,但是你评论它看起来好像它没有输出信息到stdout,而是stderr。

I suggest running a sample command directly on the command-line, and redirecting stdout and stderr to different files. 我建议直接在命令行上运行一个示例命令,并将stdout和stderr重定向到不同的文件。

growisofs [options] >stdout 2>stderr

Then you can work out which things come out on stdout and which on stderr. 然后你可以找出stdout上出现的东西以及stderr上的内容。

If the stuff you want come on stderr, change stdout=subprocess.PIPE to stderr=subprocess.PIPE and see if that works any better. 如果您想要的东西来自stderr,请将stdout=subprocess.PIPE更改为stderr=subprocess.PIPE ,看看是否有效。

Edit2: EDIT2:

You're not using threads correctly - you should be starting it - not running it directly. 你没有正确使用线程 - 你应该启动它 - 不直接运行它。

Also: 也:

gtk.gdk.threads_init()
threading.Thread.__init__(self)

is very weird - the initialiser calls should be in the initialiser - and I don't think you need to make it a gtk thread? 非常奇怪 - 初始化调用应该在初始化程序中 - 我认为你不需要使它成为一个gtk线程?

The way you call the run() method, is weird itself: 你调用run()方法的方式本身很奇怪:

core.Burning.run(self.burning, self.filechooser.get_filename(), self.listofdevices[self.combobox.get_active()])

Call instance methods through the object: 通过对象调用实例方法:

self.burning.run(self.filechooser.get_filename(), self.listofdevices[self.combobox.get_active()])

(But you should have an __init__() method) (但你应该有一个__init__()方法)

It seems to me that you are trying to run before you can walk. 在我看来,在你走路之前,你正试图奔跑。 Try writing some simple threading code, then some simple code to run growisofs and parse the output, then some simple gtk+background threading code, and only then try combining them all together. 尝试编写一些简单的线程代码,然后编写一些简单的代码来运行growisofs并解析输出,然后是一些简单的gtk +背景线程代码,然后尝试将它们组合在一起。 In fact first start writing some simple Object oriented code, so that you understand methods and object first. 实际上首先开始编写一些简单的面向对象的代码,这样你就先了解方法和对象。

eg All classes you create in python should be new-style classes, you should call super-class initialisers from your initialiser etc. 例如,您在python中创建的所有类都应该是新式类,您应该从初始化程序中调用超类初始化程序等。

Can you pass a timeout to reading from subprocess? 你可以通过超时来读取子进程吗? I guess you can because my subProcess module was used as design input for it. 我想你可以因为我的subProcess模块被用作它的设计输入。 You can use that to growiso.read(.1) and then parse and display the percentage from the outdata (or maybe errdata). 您可以使用它来growiso.read(.1),然后解析并显示outdata(或errdata)中的百分比。

You need to run the command from a separate thread, and update the gui with gobject.idle_add calls. 您需要从单独的线程运行该命令,并使用gobject.idle_add调用更新gui。 Right now you have a class "Burning" but you are using it wrong, it should be used like this: 现在你有一个类“燃烧”,但你使用它错了,它应该像这样使用:

self.burning = core.Burning(self.filechooser.get_filename(), self.listofdevices[self.combobox.get_active()], self.progressbar)
self.burning.start()

Obviously you will have to modify core.Burning. 显然你必须修改core.Burning。 Then you will have access to the progressbar so you could make a function like this: 然后您将可以访问进度条,以便您可以创建这样的函数:

def set_progress_bar_fraction(self, fraction): 
    self.progress_bar.set_fraction(fraction)

Then every percentage update call it like this: gobject.idle_add(self.set_progress_bar_fraction, fraction) 然后每个百分比更新都像这样调用: gobject.idle_add(self.set_progress_bar_fraction, fraction)

More info on pygtk with threads here . 更多信息pygtk与线程在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM