Playing a video in a python program on Raspberry Pi

I have a Raspberry Pi 3+, python 3.7, wx, and gstreamer. I wrote the following code:

import os
import wx
import wx.media


class TrainingUI(wx.Frame):
    def __init__(self, parent, title):
        super(TrainingUI, self).__init__(parent, title="Training")
        self.media_control = None
        self.init_ui()
        self.Center()

    def start_clicked(self, evt):
        print("path = " + os.path.abspath("."))
    #file_to_play = 'C:\\Users\\ralph\\files\\programming\\ImpactEmbedded\\senecadevices\\easy-shift-automation\\EasyShiftController\\LSDSHN.mpg'
    #file_to_play = '/home/pi/SenecaDevices/easy-shift-automation/EasyShiftController/sample-mp4-file.mp4'
    #file_to_play = "sample-mp4-file.mp4"
    file_to_play = "LSDSHN.mpg"
    print("Path exists: ")                  # find out if the program thinks the file exists
    print(os.path.exists(file_to_play))
    if not self.media_control.Load(file_to_play):
            print("media load failed")
    else:
            self.media_control.Play()

    def init_ui(self):
        # the main_screen_panel holds all the controls and sizers;
        # we arrange their placement on it with the sizers, defined later.
        main_screen_panel = wx.Panel(self)

        start_btn = wx.Button(main_screen_panel, label="Start")
        self.Bind(wx.EVT_BUTTON, self.start_clicked)

        control_box = wx.BoxSizer(wx.VERTICAL)
        control_box.Add(start_btn)

        # the prompt, error message, and the video panel
        video_box = wx.BoxSizer(wx.VERTICAL)
        self.media_control = wx.media.MediaCtrl(main_screen_panel, style=wx.SIMPLE_BORDER, size=wx.Size(250, 150), szBackend="") # , szBackend=wx.media.MEDIABACKEND_DIRECTSHOW)
        video_box.Add(self.media_control)

        big_box = wx.BoxSizer(wx.HORIZONTAL)
        big_box.Add(control_box)
        big_box.Add(video_box, border=10, flag=wx.ALL)

        # outermost_box has a label across the top of the main window, and a 'big box' below containing everything else

        outermost_box = wx.BoxSizer(wx.VERTICAL)
        outermost_box.Add(wx.StaticText(main_screen_panel, label="wx.media test"), flag=wx.ALIGN_CENTER_HORIZONTAL)
        outermost_box.Add(big_box)

        main_screen_panel.SetSizer(outermost_box)


def main():
    app = wx.App()
    ex = TrainingUI(None, title="traaiin")
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

I run it as follows:

project_directory $ ls -l LSDSHN.mpg
-rw-r--r-- 1 pi pi 21665792 Jul 23 11:35 LSDSHN.mpg

project_directory $ python training_ui_video.py
path = /home/pi/SenecaDevices/easy-shift-automation/EasyShiftController
Path exists: 
True
media load failed

When it runs, it immediately pops up a message dialog saying “Got an invalid playbin (error 2: No such file or directory)”; this appears on top of the main screen for the program. It has an OK button to close it. I don’t know where this error comes from, just loading the program doesn’t attempt to load the video file, is there something else indicated by “invalid playbin”?

On the main screen of the program, I have the button and a black border around the media control, as coded. When I click the ‘Start’ button, I get the text below the program invocation as shown above (starting with ‘path = …’)

I have also tried with an MP4 file, same result.

I found one SO post about the invalid playbin message; one answer said that wxPython must be compiled with the correct libraries. I didn’t compile it (I heard it takes 10 hours on a Pi), but installed it from a .whl file that I understood to be correct - “”; I also had to search for a gstreamer package and ended up with “apt-get install python-wxgtk-media3.0”, which eliminated a message I was getting when attempting to import wx.media.

What can I do to play video within a python program as above, either with this approach or another one. Ideally, I would have control programmatically over start, stop, pause, play different videos, etc.

Hi,

I looked into you code and the Demo code of wxPython “More Windows/Controls - MediaCtrl”.
After calling Load, I think you have to wait for a while to call Play or wait for wx.media.EVT_MEDIA_LOADED.

For example, split the start_clicked function into two steps like this:

    def load_file(self, file_to_play):
        self.media_control.Load(file_to_play)

    def start_clicked(self, evt):
        self.media_control.Play()

I thank you for the tip, which I should have picked up on and did not. I now have:

 def start_clicked(self, evt):
        print("path = " + os.path.abspath("."))
        file_to_play = 'C:\\Users\\ralph\\files\\programming\\ImpactEmbedded\\senecadevices\\easy-shift-automation\\EasyShiftController\\sample-mp4-file.mp4'
        #file_to_play = '/home/pi/SenecaDevices/easy-shift-automation/EasyShiftController/sample-mp4-file.mp4'
        #file_to_play = "sample-mp4-file.mp4"
        #file_to_play = "LSDSHN.mpg"
        print("Path exists: ")                  # find out if the program thinks the file exists
        print(os.path.exists(file_to_play))
        if not self.media_control.Load(file_to_play):
            print("media load failed")

    def play_loaded_video(self, evt):
        print("waiting 3 to play")
        time.sleep(3)
        self.media_control.Play()

    def init_ui(self):
        # the main_screen_panel holds all the controls and sizers;
        # we arrange their placement on it with the sizers, defined later.
        main_screen_panel = wx.Panel(self)

        start_btn = wx.Button(main_screen_panel, label="Start")
        self.Bind(wx.EVT_BUTTON, self.start_clicked)

        control_box = wx.BoxSizer(wx.VERTICAL)
        control_box.Add(start_btn)

        # the prompt, error message, and the video panel
        video_box = wx.BoxSizer(wx.VERTICAL)
        self.media_control = wx.media.MediaCtrl(main_screen_panel, style=wx.SIMPLE_BORDER, size=wx.Size(250, 150), szBackend="") # , szBackend=wx.media.MEDIABACKEND_DIRECTSHOW)
        video_box.Add(self.media_control)
        self.Bind(wx.media.EVT_MEDIA_LOADED, self.play_loaded_video, self.media_control)

as part of my code, but it still doesn’t work as expected. I never get the print message from play_loaded_video(). The video flashes up briefly – just a split second – then disappears again, though it will play normally on a Windows video player.

The self in self.Bind(... refers to the whole class as self, and the whole class extends wx.Frame - is that the right object to be bound? The documentation is a little sparse there, it refers to passing the signal to the media control’s “parent”, but doesn’t really define that. The immediate UI parent is a layout panel, put in another layout panel, etc. But it doesn’t make sense to me to try to bind the layouts, even if I knew where to put the attached function.

Does anyone see what’s wrong here?

Hi arcy,

There are two points that need to be fixed.
In the Demo code of wxPython “More Windows/Controls - MediaCtrl”, it says that

the wx.media.EVT_MEDIA_LOADED event is not sent with the Windows default backend MEDIABACKEND_DIRECTSHOW

So, you should try with another backend. For Linux, maybe,

        self.media_control = wx.media.MediaCtrl(main_screen_panel,
            style=wx.SIMPLE_BORDER, size=wx.Size(250, 150),
            szBackend=wx.media.MEDIABACKEND_GSTREAMER)

Calling time.sleep in the main thread is not a good idea. Like humans, when parents are busy, their children (backend threads in this case) become lazy and do not work. I said “wait for a while”, but it literally meant just waiting. :slightly_smiling_face:

It’s OK. Any of the following code will work.

        self.Bind(wx.media.EVT_MEDIA_LOADED, self.play_loaded_video, self.media_control)
        self.media_control.Bind(wx.media.EVT_MEDIA_LOADED, self.play_loaded_video)
        self.Bind(wx.media.EVT_MEDIA_LOADED, self.play_loaded_video)

In the Demo code of wxPython “More Windows/Controls - MediaCtrl” …

Could you please give me a link to this? I appreciate the information, and wish they provided it with the API of the control (along with a number of other things)

literally meant just waiting

Was trying to give the UI time to do things; I wouldn’t put a sleep in code that might be on the UI thread except when playing around trying to get things to work…

It’s OK. Any of the following code will work.

Again, thanks for that info, but it’s frustrating. Computer programs are deterministic, the calls to functions/methods mean something. I have a hard time telling what this one means if Bind can be called on ‘self’ (in my case the frame), or on the control, or called with 2 parameters or three, and all mean the same thing.

I would expect Bind to be called on the object which is going to get the signal/call after the media is loaded, and I would expect its parameters to be used for something. It looks like there is a third parameter that can be there or not, so why is it ever there? Does the frame use it to identify the control about which the event is sent? That’d be great, but I’d rather not guess about it. And how does it work if the MediaCtrl instance itself gets the call? Does it pass it to its own parent? Again, explicit information would be nice.

As it is, leaving szBackEnd as “”, which is supposed to “let MediaCtrl choose the backend”, doesn’t work; I finally found MEDIABACKEND_WMP10 worked on Win10, going through the possibles one at a time. I suppose for platform independence I’ll have to test the OS I’m on to set it.

But it’s working that way. Thanks.

Related to this post (Making the demo more visible), It becomes now not as clear where the demo code is as it used to be… Anyway, if you installed python in “C:\Python38”, the wx demo launcher is “C:\Python38\Scripts\wxdemo.exe”.

To explain Bind, let’s take your code as an example:

With the above code, if self:Frame has a lot of buttons, pressing any of those buttons will trigger wx.EVT_BUTTON and call self.start_clicked.

But if you write as below, only the start_btn button calls self.start_clicked.

    start_btn.Bind(wx.EVT_BUTTON, self.start_clicked)

This is equivalent to the following, however, this style is old-fashion.

    self.Bind(wx.EVT_BUTTON, self.start_clicked, start_btn)

I hope it will work on Linux!

Well, I found the demo code.

I finally decided that I had to go ahead and build wxPython to get this to work, so I did. I followed the instructions (which are very good) and built wxPython on the RPi 3+. It took 3 tries and a number of hours, but I now have a wheel there.

I found the demo code (MediaCtrl.py) and attempted to run it; it fails, saying that MediaCtrl is not supported on the platform. The console says that has an invalid ‘playbin’, no such file. I changed the file to use the GSTREAMER backend explicitly, with the same result.

I saw a reference somewhere that said that MediaCtrl required a specific version of GStreamer, or at least implied it heavily. But I do wish I could find someone who had actually made this work on the RPi, rather than just having to try this and that.