python-mpv icon indicating copy to clipboard operation
python-mpv copied to clipboard

[Windows] Controls do not work in PyQT (?)

Open davFaithid opened this issue 5 years ago • 9 comments

Take this code for instance.

    class MainWin(QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.container = QWidget(self)
            self.setCentralWidget(self.container)
            self.container.setAttribute(Qt.WA_DontCreateNativeAncestors)
            self.container.setAttribute(Qt.WA_NativeWindow)
            self.setWindowTitle(title) 
            self.resize(1000, 563)

            player = mpv.MPV(wid=str(int(self.container.winId())), ytdl=True, player_operation_mode='pseudo-gui',
                 script_opts='osc-layout=box,osc-seekbarstyle=bar,osc-deadzonesize=0,osc-minmousemove=3',
                 input_default_bindings=True,
                 input_vo_keyboard=True,
                 osc=True)
            @player.on_key_press('esc')
            def my_key_binding():
                print('')
                sys.exit()
            player.play(url)
        #   player.wait_for_playback()
            del player

It works pretty well, it plays video in the window and it read the video from the url variable and the window title from title. Yet despite me having set not only the pseudo-gui and keyboard bindings, neither of them initiate so inside my window is video playing with no keyboard or in-window controls. I am relatively new to PyQt, so maybe this is a simple to fix issue. Another oddity with the PyQt integration is that wait_for_playback() is automatic, and actually trying to call it prevents the video from playing.

I really like this module, and it is very handy for several of my video projects (ie this)

davFaithid avatar Feb 03 '20 23:02 davFaithid

I don't know Qt that well myself, but I guess the problem is that Qt catches all input events and they never get forwarded to mpv. My first idea on how to make this work without rewriting all of libmpv's input bindings would be to catch all key press events on the player QWidget and forward them into mpv using the keypress command. There is also a mouse command if you want mouse input as well.

The reason wait_for_playback doesn't work is that you were calling it in the constructor of your Qt window. This means it would wait before even displaying the window. Since Qt has its own main loop (the app.exec()), there shouldn't be any need for you to call wait_for_playback. Either have the user close the window through user interaction, or you register an event callback with mpv using MPV.register_event_callback that looks for event['event_id'] in (MpvEventID.SHUTDOWN, MpvEventID.END_FILE) and tells qt to exit when it sees one.

jaseg avatar Feb 04 '20 09:02 jaseg

Thank you very much @jaseg for your helpful answer. I have tried using the keypress and keybind commands yet they do not seem to work

#First I tried this
player.command('keybind esc stop')
player.command('keybind right seek 1 relative')
#And then I tried
player.keybind('esc stop')
player.keybind('right seek 1 relative')

Example errors I get:

Traceback (most recent call last):
  File "main.py", line 131, in <module>
    play(url, title)
  File "main.py", line 127, in play
    win = MainWin()
  File "main.py", line 119, in __init__
    player.keybind('esc stop')
  File "C:\Users\davfa\AppData\Local\Programs\Python\Python38-32\lib\site-packages\mpv.py", line 1335, in __getattr__
    return self._get_property(_py_to_mpv(name), lazy_decoder)
  File "C:\Users\davfa\AppData\Local\Programs\Python\Python38-32\lib\site-packages\mpv.py", line 1313, in _get_property
    cval = _mpv_get_property(self.handle, name.encode('utf-8'), fmt, out)
  File "C:\Users\davfa\AppData\Local\Programs\Python\Python38-32\lib\site-packages\mpv.py", line 126, in raise_for_ec
    raise ex(ec, *args)
AttributeError: ('mpv property does not exist', -8, (<MpvHandle object at 0x03BD1E38>, b'keybind', 6, <ctypes.c_char_Array_16 object at 0x03BD1E80>))
Traceback (most recent call last):
  File "main.py", line 135, in <module>
    play(url, title)
  File "main.py", line 131, in play
    win = MainWin()
  File "main.py", line 119, in __init__
    @player.command('keybind esc stop')
  File "C:\Users\davfa\AppData\Local\Programs\Python\Python38-32\lib\site-packages\mpv.py", line 719, in command
    _mpv_command(self.handle, (c_char_p*len(args))(*args))
  File "C:\Users\davfa\AppData\Local\Programs\Python\Python38-32\lib\site-packages\mpv.py", line 126, in raise_for_ec
    raise ex(ec, *args)
ValueError: ('Invalid value for mpv parameter', -4, (<MpvHandle object at 0x03B21E80>, <mpv.c_char_p_Array_2 object at 0x03B21EC8>))

And when I tried using @player it errored

File "main.py", line 121
    player.play(url)
    ^
SyntaxError: invalid syntax

Do you have any further suggestions? Thank you.

davFaithid avatar Feb 06 '20 01:02 davFaithid

@jaseg I have tried your recommendations and have written

...
from pynput import keyboard 

def play(url, title):
    #From https://stackoverflow.com/q/9377914/
    class TitleBar(QWidget):

      def __init__(self, parent):
        super(MyBar, self).__init__()
        self.parent = parent
        print(self.parent.width())
        self.layout = QHBoxLayout()
        self.layout.setContentsMargins(0,0,0,0)

        btn_size = 35

        self.btn_close = QPushButton("x")
        self.btn_close.clicked.connect(self.btn_close_clicked)
        self.btn_close.setFixedSize(btn_size,btn_size)
        self.btn_close.setStyleSheet("background-color: red;")

        self.btn_min = QPushButton("-")
        self.btn_min.clicked.connect(self.btn_min_clicked)
        self.btn_min.setFixedSize(btn_size, btn_size)
        self.btn_min.setStyleSheet("background-color: gray;")

        self.btn_max = QPushButton("+")
        self.btn_max.clicked.connect(self.btn_max_clicked)
        self.btn_max.setFixedSize(btn_size, btn_size)
        self.btn_max.setStyleSheet("background-color: gray;")

        self.title.setFixedHeight(35)
        self.title.setAlignment(Qt.AlignCenter)
        self.layout.addWidget(self.btn_min)
        self.layout.addWidget(self.btn_max)
        self.layout.addWidget(self.btn_close)

        self.setLayout(self.layout)

        self.start = QPoint(0, 0)
        self.pressing = False

      def resizeEvent(self, QResizeEvent):
        super(MyBar, self).resizeEvent(QResizeEvent)
        self.title.setFixedWidth(self.parent.width())

      def mousePressEvent(self, event):
        self.start = self.mapToGlobal(event.pos())
        self.pressing = True

      def mouseMoveEvent(self, event):
        if self.pressing:
            self.end = self.mapToGlobal(event.pos())
            self.movement = self.end-self.start
            self.parent.setGeometry(self.mapToGlobal(self.movement).x(),
                                self.mapToGlobal(self.movement).y(),
                                self.parent.width(),
                                self.parent.height())
            self.start = self.end

      def mouseReleaseEvent(self, QMouseEvent):
        self.pressing = False


      def btn_close_clicked(self):
        self.parent.close()

      def btn_max_clicked(self):
        self.parent.showMaximized()

      def btn_min_clicked(self):
        self.parent.showMinimized()

    class MainWin(QMainWindow):
        def on_press(self,key):
            print('{0} pressed'.format(
        key))
            if key == keyboard.Key.right:
                self.player.command('seek', '0.2', 'absolute+keyframes')

        def on_release(self, key):
            print('{0} released'.format(
        key))
            if key == keyboard.Key.esc:
                try:
                    print("Quitting")
                    sys.exit('quit')
                except KeyboardInterrupt:
                    sys.exit('quit')
        def __init__(self, parent=None):
            super().__init__(parent)
            self.container = QWidget(self)
            self.setCentralWidget(self.container)
            self.container.setAttribute(Qt.WA_DontCreateNativeAncestors)
            self.container.setAttribute(Qt.WA_NativeWindow)
            self.setWindowTitle(title) 
            self.resize(1000, 563)
            self.player = mpv.MPV(wid=str(int(self.container.winId())), ytdl=True, player_operation_mode='pseudo-gui',
            script_opts='osc-layout=box,osc-seekbarstyle=bar,osc-deadzonesize=0,osc-minmousemove=3',
            input_default_bindings=True,
            input_vo_keyboard=True,
            osc=True) #
            #player.play(url)
            # Collect events until released
            with keyboard.Listener(
            on_press=self.on_press,
            on_release=self.on_release) as listener:
                Thread(target = self.player.play, args=(url,)).start()
                listener.join()
                #Process(target=player.play, args=(url,)).start()
                
            #player.wait_for_playback()
            del self.player
            #@player.command('keybind esc stop')
            #def passthru():
            #    pass
            #@player.command('keybind right seek 1 relative')
            #def passthru():
            #    pass
    app = QApplication(sys.argv)
    import locale
    locale.setlocale(locale.LC_NUMERIC, 'C')
    win = MainWin()
    win.show()
    sys.exit(app.exec_())
play(url, title)

it registers commands, but does them all in a seperate process, which upon exiting plays the video. I couldn't seem to get keypress or keybind to give me anything but my prior error.

  File "main.py", line 137, in __init__
    self.player.command('keybind', 'right', 'seek', '0.2', 'absolute+keyframes')
  File "C:\Users\davfa\AppData\Local\Programs\Python\Python38-32\lib\site-packages\mpv.py", line 719, in command
    _mpv_command(self.handle, (c_char_p*len(args))(*args))
  File "C:\Users\davfa\AppData\Local\Programs\Python\Python38-32\lib\site-packages\mpv.py", line 126, in raise_for_ec
    raise ex(ec, *args)
ValueError: ('Invalid value for mpv parameter', -4, (<MpvHandle object at 0x03256850>, <mpv.c_char_p_Array_6 object at 0x03256898>))

Do you have suggestions? Thank you.

davFaithid avatar Feb 22 '20 03:02 davFaithid

Are you on windows? For me the inbuilt libmpv controls work on Linux but not on Windows. This is the output on windows: Mpv Output when I run the mpv pyqt example from the readme (without the vo='x11' option of course) PyQt Mpv Code. It seems strange that qt would be catching the input on windows but not on linux but who knows. Do you have some ideas how this could be investigated further? I would like to have a more in-depth look at this.

Laeri avatar Feb 23 '20 09:02 Laeri

I found a stackoverflow post on keypresses in PyQt, I will try to tweak it to input to mpv. https://stackoverflow.com/q/38507011/

player commands aren't working

    self.player.command('seek', '0.2', 'absolute+keyframes')
AttributeError: 'MainWin' object has no attribute 'player'
        def __init__(self, parent=None): #, parent=None
            super(MainWin, self).__init__(parent) #parent
            self.container = QWidget(self)
            self.setCentralWidget(self.container)
            self.container.setAttribute(Qt.WA_DontCreateNativeAncestors)
            self.container.setAttribute(Qt.WA_NativeWindow)
            self.setWindowTitle(title) 
            self.resize(1000, 563)
            self.player = mpv.MPV(wid=str(int(self.container.winId())), ytdl=True, player_operation_mode='pseudo-gui',
            script_opts='osc-layout=box,osc-seekbarstyle=bar,osc-deadzonesize=0,osc-minmousemove=3',
            input_default_bindings=True,
            input_vo_keyboard=True,
            osc=True) #
            self.player.play(url)
            #player.wait_for_playback()
            del self.player
        def keyPressEvent(self, event):
            if event.key() == Qt.Key_Q:
                print("Quitting")
                sys.exit('quit')
            elif event.key() == Qt.Key_Right:
                self.player.command('seek', '0.2', 'absolute+keyframes')
            event.accept()

davFaithid avatar Feb 24 '20 01:02 davFaithid

Sorry in my gist I forgot the options: player_operation_mode='pseudo-gui' , input_default_bindings=True, input_vo_keyboard=True,osc=True. But on Linux (Manjaro Linux x86_64, 5.3.18-1-MANJARO), mpv 0.32.0, ffmpeg version: n4.2.2 the inbuilt gui of libmpv is shown without problem and I can interact with it without forwarding any events to mpv. Therefore this might rather be a problem with the libmpv dll on Windows or what do you think?

Laeri avatar Feb 24 '20 07:02 Laeri

That seems very likely, but I cannot run any VM because of credential guard (whatever that is).

I'll continue tweaking the code; and if I cannot find a solution then I can mark it up to libmpv on windows

davFaithid avatar Feb 24 '20 16:02 davFaithid