2

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?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
voron.run
  • 47
  • 7
  • Theoretically, it seems that it should be possible by getting a QMediaStreamsControl interface using `player.service().requestControl('org.qt-project.qt.mediastreamscontrol/5.0')`, but all my tests were unsuccessful (it always returned a basic QMediaControl, which doesn't provide an interface to do that), so it *might* mean that it isn't available on PyQt (yet). I sent an email to the PyQt mailing list (which is followed by the official PyQt maintainer too) to have some further information. – musicamante Apr 17 '20 at 23:34

1 Answers1

2

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:

  • If you use pyside2 the solution is simple.

  • If you use pyqt5 you will have to modify its source code, compile it and install it. Hopefully with the @musicamante report class QMediaStreamsControl is exposed in future releases of pyqt5

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thank you very much for the detailed answer and especially for the simple solution for pyside2. – voron.run Apr 19 '20 at 15:17
  • The PyQt maintainer just wrote on the mailing list that the current latest PyQt5 snapshot implements all the missing QMediaControl subclasses, so, if you can upgrade, you should be able to use it. – musicamante Apr 22 '20 at 10:50