pythonpyqtpyqt5qtmultimedia

QMediaPlayer. How to play video with multiple audio?


There is a video file with two audio tracks:

  Duration: 01:05:09.12, start: 0.000000, bitrate: 2781 kb/s
    Stream #0:0: Video: mpeg4 (Advanced Simple Profile) (XVID / 0x44495658), yuv420p, 720x400 [SAR 1:1 DAR 9:5], 1998 kb/s, 25 fps, 25 tbr, 25 tbn, 25 tbc
    Stream #0:1: Audio: ac3 ([0] [0][0] / 0x2000), 48000 Hz, 5.1(side), fltp, 384 kb/s
    Stream #0:2: Audio: ac3 ([0] [0][0] / 0x2000), 48000 Hz, 5.1(side), fltp, 384 kb/s

Playing it with this code:

self.player = QMediaPlayer()
self.player.setMedia(QMediaContent(QUrl.fromLocalFile(fileName)))
self.player.play()

Video and first audio track is played. Нow to switch to the second audio track?


Solution

  • As @musicamante points out in the comments, the solution is to access the QMediaStreamsControl but PyQt5 does not expose it.

    Instead PySide2 does expose it and the solution is to cast using shiboken2:

    import os
    
    from PySide2 import QtCore, QtWidgets, QtMultimedia, QtMultimediaWidgets
    
    import shiboken2
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            video_widget = QtMultimediaWidgets.QVideoWidget()
            self.player = QtMultimedia.QMediaPlayer(
                self, QtMultimedia.QMediaPlayer.VideoSurface
            )
            file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test5.mkv")
            self.player.setMedia(
                QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file))
            )
            self.player.setVideoOutput(video_widget)
            self.player.play()
            self.setCentralWidget(video_widget)
    
            control = self.player.service().requestControl(
                "org.qt-project.qt.mediastreamscontrol/5.0"
            )
            qptr = shiboken2.getCppPointer(control)[0]
            self.qcontrol = shiboken2.wrapInstance(qptr, QtMultimedia.QMediaStreamsControl)
            self.resize(640, 480)
    
        def contextMenuEvent(self, event):
            menu = QtWidgets.QMenu()
            group = QtWidgets.QActionGroup(menu)
            group.setExclusive(True)
            index = 0
            for i in range(self.qcontrol.streamCount()):
                t = self.qcontrol.streamType(i)
                if t == QtMultimedia.QMediaStreamsControl.AudioStream:
                    action = menu.addAction("Audio-{}".format(index))
                    action.setCheckable(True)
                    if self.qcontrol.isActive(i):
                        action.setChecked(True)
                    action.setData(i)
                    menu.addAction(action)
                    index += 1
            action = menu.exec_(self.mapToGlobal(event.pos()))
            if action is not None:
                i = action.data()
                self.qcontrol.setActive(i, True)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())
    

    In the case of pyqt5 you should use sip with the following code:

    import os
    
    from PyQt5 import QtCore, QtWidgets, QtMultimedia, QtMultimediaWidgets
    
    import sip
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            video_widget = QtMultimediaWidgets.QVideoWidget()
            self.player = QtMultimedia.QMediaPlayer(
                self, QtMultimedia.QMediaPlayer.VideoSurface
            )
            file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test5.mkv")
            self.player.setMedia(
                QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file))
            )
            self.player.setVideoOutput(video_widget)
            self.player.play()
            self.setCentralWidget(video_widget)
    
            control = self.player.service().requestControl(
                "org.qt-project.qt.mediastreamscontrol/5.0"
            )
            self.qcontrol = sip.cast(control, QtMultimedia.QMediaStreamsControl)
            self.resize(640, 480)
    
        def contextMenuEvent(self, event):
            menu = QtWidgets.QMenu()
            group = QtWidgets.QActionGroup(menu)
            group.setExclusive(True)
            index = 0
            for i in range(self.qcontrol.streamCount()):
                t = self.qcontrol.streamType(i)
                if t == QtMultimedia.QMediaStreamsControl.AudioStream:
                    action = menu.addAction("Audio-{}".format(index))
                    action.setCheckable(True)
                    if self.qcontrol.isActive(i):
                        action.setChecked(True)
                    action.setData(i)
                    menu.addAction(action)
                    index += 1
            action = menu.exec_(self.mapToGlobal(event.pos()))
            if action is not None:
                i = action.data()
                self.qcontrol.setActive(i, True)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())
    

    But as pointed out by QMediaStreamsControl is not available in PyQt5, so a solution is to expose it and for this you must:

    1. Download PyQt5 source code: https://pypi.python.org/packages/source/P/PyQt5/PyQt5-5.14.2.tar.gz
    2. Create the file qmediastreamscontrol.sip in "sip/QtMultimedia" folder of the PyQt5 source code.

      qmediastreamscontrol.sip

      class QMediaStreamsControl : QMediaControl
      {
      %TypeHeaderCode
      #include <qmediastreamscontrol.h>
      %End
      
      public:
          enum StreamType { 
              UnknownStream, 
              VideoStream, 
              AudioStream, 
              SubPictureStream, 
              DataStream 
          };
      
          virtual ~QMediaStreamsControl();
          virtual int streamCount() = 0;
          virtual QMediaStreamsControl::StreamType streamType(int streamNumber) = 0;
          virtual QVariant metaData(int streamNumber, const QString &key) = 0;
          virtual bool isActive(int streamNumber) = 0;
          virtual void setActive(int streamNumber, bool state) = 0;
      
      signals:
          void streamsChanged();
          void activeStreamsChanged();
      
      protected:
      %If (Qt_5_6_1 -)
          explicit QMediaStreamsControl(QObject *parent /TransferThis/ = 0);
      %End
      %If (- Qt_5_6_1)
          QMediaStreamsControl(QObject *parent /TransferThis/ = 0);
      %End
      };
      
    3. Add %Include qmediastreamscontrol.sip to the end of file sip/QtMultimedia/QtMultimediamod.sip

    4. Compile and install PyQt5 using the modified source code.

    In conclusion: