0

Running on Python 3.x on Windows 10

I'm working on a script to help automate compiling a .tiff image sequence into a video. I'm using wxPython to build the GUI. First I create the window class and set a global variable for the window.

global main_window
main_window = self

Then I have a function I use to write to the statusbar and also print the values to console(occasionally I also add code to write a log file from the text I send to this function).

def update_status_bar(window, text):
    status = str(text)
    window.statusbar.SetStatusText(status)
    print(status)
    window.Refresh()
    window.Update()
    wx.SafeYield(win=None, onlyIfNeeded=False)

This is the moviePy function I wrote to convert the image sequence into ta video.

def video_from_sequence(image_sequence, video, fps):
    img = []
    update_status_bar(main_window, 'Getting Image Directory')
    path = os.path.dirname(os.path.realpath(image_sequence))
    print(path)
    update_status_bar(main_window, 'Getting List of Image Files')
    for root, dirs, files in os.walk(path):
        for file in files:
            if file.endswith('.tiff'):
                img.append(file)
    os.chdir(path)
    update_status_bar(main_window, 'Creating Video From Image Sequence')
    clip = ImageSequenceClip(img, fps=fps)
    update_status_bar(main_window, clip.write_videofile(video, fps=fps))

The print to console does show the progress, however the statusbar is not populating with the current progress of the process. Because I'd like to eventually have this run as a .pyw, where the statusbar shows the progress, it's important to me that I have the progressbar show up in the statusbar, however I'm having trouble finding out a way to do this.

UPDATE (06/01/2020):

I've managed to use 2 functions to start and stop the progress bar, and created the status bar with 2 panels instead of one.

My code within the MainWindow class for the status bar has been changed to:

self.statusbar = self.CreateStatusBar(2)
self.progress_bar = wx.Gauge(self.statusbar, -1, size=(280,25), style=wx.GA_PROGRESS)
self.progress_bar_active = False
self.Show()
self.progress_bar.SetRange(50)
self.progress_bar.SetValue(0)

My function to start the animation:

def start_busy_statusbar(window):
    window.count = 0
    window.proc = subprocess.Popen(['ping', '127.0.0.1', '-i', '0.2'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    while True:
        wx.Yield()
        try:
            list_data = window.proc.stdout.readline()
            wx.Yield()
        except:
            break
        if len(list_data) == 0:
            break
        window.progress_bar.Pulse()
        wx.Yield()
        window.count += 1

And my function to stop the animation:

def stop_busy_statusbar(window):
    window.progress_bar.Destroy()
    window.progress_bar = wx.Gauge(window.statusbar, -1, size=(280, 25), style=wx.GA_PROGRESS)

Which does feel a bit crude, but it works.

So my convert() function that calls the video_from_sequence() function looks like this:

    def convert(self, event):
        start_busy_statusbar(main_window)
        image_sequence = str(self.text_image_sequence_dir.GetValue())
        original_video = str(self.text_original_video_dir.GetValue())
        if image_sequence.endswith('.tiff') and original_video.endswith('.mkv'):
            try:
                new_video = str(original_video)[:-4] + '_1080p.mkv'
                temp_video = str(original_video)[:-4] + '_temp.mkv'
                print(image_sequence)
                print(original_video)
                print(new_video)
                fps = get_frame_rate(original_video)
                print(fps)
                video_from_sequence(image_sequence, temp_video, fps)

            except FileNotFoundError as e:
                e = str(e).replace('Errno 2] ', '')
                e = e.replace('directory:', 'directory:\n')
                warning(e)
            update_status_bar(self, 'Finished')
        else:
            warning('You must enter valid paths for both a tiff sequence and original video.')
        stop_busy_statusbar(main_window)

What I'm running into now, is that the Window locks up while it's processing the image sequence. It's been suggested that I use a seperate thread or utilize the wx.Yield. I'm not quite sure how to execute these things. I've tried CallAfter(function(vars)) but that doesn't seem to work.

I've copied code snippets demonstrating how this works in wxPython, but I don't really understand how it works, and can't get it to work in my script.

Pyrometheous
  • 65
  • 2
  • 14

1 Answers1

1

Here is a very old example of implementing a progress bar in the statusbar.
It should give you enough to work with.

import wx
import subprocess
class MainFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, title='Statusbar progress')
        panel = wx.Panel(self)
        self.start_btn = wx.Button(panel, label='Start')
        self.start_btn.Bind(wx.EVT_BUTTON, self.OnStart)
        self.stop_btn = wx.Button(panel, label='Stop')
        self.stop_btn.Bind(wx.EVT_BUTTON, self.OnStop)
        self.Bind(wx.EVT_CLOSE, self.OnExit)
        self.text = wx.TextCtrl(panel, -1, 'Type text here')
        btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
        btn_sizer.Add(self.start_btn)
        btn_sizer.Add(self.stop_btn)
        btn_sizer.Add(self.text)
        panel.SetSizer(btn_sizer)
        self.statusbar = self.CreateStatusBar(3)
        self.text1 = wx.StaticText(self.statusbar,-1,("Static text"))
        self.text2 = wx.StaticText(self.statusbar,-1,("Count Text"))
        self.progress_bar = wx.Gauge(self.statusbar, -1, style=wx.GA_HORIZONTAL|wx.GA_SMOOTH)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.text1, 0, wx.ALL, 5)
        sizer.Add(self.text2, 1, wx.ALL, 5)
        sizer.Add(self.progress_bar, 2, wx.ALL, 5)
        self.statusbar.SetSizer(sizer)
        self.Show()
        self.progress_bar.SetRange(50)
        self.progress_bar.SetValue(0)

    def OnStart(self,event):
        self.start_btn.Enable(False)
        self.text1.SetLabelText("Started")
        self.count = 0
        self.proc = subprocess.Popen(['ping','127.0.0.1','-i','0.2'],stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        while True:
            try:
                list_data = self.proc.stdout.readline()
            except:
                break
            if len(list_data) == 0:
                break
            self.progress_bar.Pulse()
            self.count+=1
            self.text2.SetLabelText("Count "+str(self.count))
            wx.GetApp().Yield()

    def OnStop(self,event):
        self.start_btn.Enable(True)
        self.text1.SetLabelText("Stopped")
        try:
            self.proc.kill()
        except:
            pass

    def OnExit(self,event):
        try:
            self.proc.kill()
        except:
            pass
        self.Destroy()

if __name__ == '__main__':
    app = wx.App()
    frame = MainFrame()
    app.MainLoop()
Rolf of Saxony
  • 21,661
  • 5
  • 39
  • 60
  • Thanks for this. I'm not sure how to call this during a function in between starting a process, and the process ending though. Do I add the `def __init__(self)` to the current window I have? Or maybe just add the functions of that class to the class I currently have for the window? – Pyrometheous Jun 01 '20 at 15:43
  • Okay, I've managed to get the progressbar animation started, however I can't get it to stop. The `.proc.kill()` doesn't actually seem to kill the status bar. I also noticed that the progress bar freezes when I start a longer process (in this case compiling the frames into a video). – Pyrometheous Jun 01 '20 at 16:26
  • Without being able to see your code it's almost impossible to comment. However the `proc.kill` is simply terminating a subprocess, so your equivalent would be stopping the image conversion. The freezing will occur to the whole gui when you process a `long running task`. You can either `wx.Yield()` at opportune moments or if you're threading the progressbar, `wx.Yield` on a regular basis and create a `stop` method for the thread. – Rolf of Saxony Jun 01 '20 at 16:52
  • I've found that I can use `window.progress_bar.Destroy()` and `window.progress_bar = wx.Gauge(window.statusbar, -1, size=(280, 25), style=wx.GA_PROGRESS)` to get the animation to stop. I'm not sure what the appropriate places for `wx.Yield()` or `wx.Yield` would be. I'll keep playing around with it though, I'd very much like to avoid the freezing if I can. Or maybe I just need to learn how to run it in another thread. – Pyrometheous Jun 01 '20 at 17:20
  • I've updated the page to include more of my code to evaluate. – Pyrometheous Jun 01 '20 at 19:03
  • @Pyrometheous The key to this is not the progress bar, that's simple, it's what's the command you're using to perform the processing i.e. `video_from_sequence`. Can it be interrupted, does it provide its own progress that can be interpreted. Are you using `ffmpeg` on the command line? – Rolf of Saxony Jun 04 '20 at 11:38