QVideoWidget 内容不是从小部件中获取的

QVideoWidget content isn't grabed from widget

我创建了一个简单的媒体播放器。我想添加为显示的视频制作快照的功能。为此,我使用 'self.videoWidget.grab()' 函数。但似乎 grab() 不能正常工作,因为我得到的不是快照,而是一张颜色为 wiget 背景的图片。如果我用 'snapshot = self.grab()' 替换 'self.videoWidget.grab()' 我得到了小部件的快照,但上面没有 videoWidget 内容(添加了图片)。我去抛出类似的问题,但一无所获。我是 PyQt5 的新手,所以我希望解决方案很明显,但我一个人没能找到它。

from PyQt5.QtWidgets import QPushButton, QStyle, QVBoxLayout, QWidget, QFileDialog, QLabel, QSlider, QHBoxLayout
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtCore import QUrl, Qt

class MediaWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Media widget")
        self.initUi()
        self.show()

    def initUi(self):
        # Create media player
        self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
        self.videoWidget = QVideoWidget()
        self.mediaPlayer.setVideoOutput(self.videoWidget)
        self.mediaPlayer.durationChanged.connect(self.durationChanged)
        self.mediaPlayer.positionChanged.connect(self.positionChanged)
        self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)

        # Open button configuration
        openButton = QPushButton("Open video")
        openButton.setToolTip("Open video file")
        openButton.setStatusTip("Open video file")
        openButton.setFixedHeight(24)
        openButton.clicked.connect(self.openFile)

        # Snapshot button configuration
        self.snapshotButton = QPushButton("Get snapshot")
        self.snapshotButton.setEnabled(False)
        self.snapshotButton.setShortcut("Ctrl+S")
        self.snapshotButton.setToolTip("Get snapshot (Ctrl+S)")
        self.snapshotButton.setFixedHeight(24)
        self.snapshotButton.clicked.connect(self.getSnapshot)

        # Play button configuration
        self.playButton = QPushButton()
        self.playButton.setEnabled(False)
        self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.playButton.clicked.connect(self.play)

        # Play button configuration
        self.videoSlider = QSlider(Qt.Horizontal)
        self.videoSlider.setRange(0, 0)
        self.videoSlider.sliderMoved.connect(self.setPosition)

        # Create layouts to place inside widget
        contentLayout = QVBoxLayout()
        controlsLayout = QHBoxLayout()
        controlsLayout.addWidget(self.playButton)
        controlsLayout.addWidget(self.snapshotButton)
        controlsLayout.addWidget(self.videoSlider)
        contentLayout.addWidget(self.videoWidget)
        contentLayout.addLayout(controlsLayout)
        contentLayout.addWidget(openButton)
        self.setLayout(contentLayout)

    def openFile(self):
        fileName = QFileDialog.getOpenFileName(self, "Open video", "/home")[0]
        if fileName != '':
            self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(fileName)))
            self.playButton.setEnabled(True)
            self.snapshotButton.setEnabled(True)

    def getSnapshot(self):
        snapshot = self.videoWidget.grab()
        snapshot.save("TestFileName", "jpg")

    def play(self):
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.mediaPlayer.pause()
        else:
            self.mediaPlayer.play()

    def mediaStateChanged(self, state):
        if state == QMediaPlayer.PlayingState:
            self.playButton.setIcon(
                    self.style().standardIcon(QStyle.SP_MediaPause))
        else:
            self.playButton.setIcon(
                    self.style().standardIcon(QStyle.SP_MediaPlay))

    def durationChanged(self, duration):
        self.videoSlider.setRange(0, duration)

    def positionChanged(self, position):
        self.videoSlider.setValue(position)

    def setPosition(self, position):
        self.mediaPlayer.setPosition(position) 

如何window被抓取

window

中实际发生了什么

一个可能的解决方案是实施显示最后一帧的 QAbstractVideoSurface

class SnapshotVideoSurface(QAbstractVideoSurface):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._current_frame = QImage()

    @property
    def current_frame(self):
        return self._current_frame

    def supportedPixelFormats(self, handleType=QAbstractVideoBuffer.NoHandle):
        formats = [QVideoFrame.PixelFormat()]
        if handleType == QAbstractVideoBuffer.NoHandle:
            for f in [
                QVideoFrame.Format_RGB32,
                QVideoFrame.Format_ARGB32,
                QVideoFrame.Format_ARGB32_Premultiplied,
                QVideoFrame.Format_RGB565,
                QVideoFrame.Format_RGB555,
            ]:
                formats.append(f)
        return formats

    def present(self, frame):
        self._current_frame = frame.image()
        return True

然后

def initUi(self):
    self.snapshotVideoSurface = SnapshotVideoSurface(self)
    # Create media player
    self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
    self.videoWidget = QVideoWidget()
    # self.mediaPlayer.setVideoOutput(self.videoWidget)
    self.mediaPlayer.setVideoOutput(
        [self.videoWidget.videoSurface(), self.snapshotVideoSurface]
    )
    self.mediaPlayer.durationChanged.connect(self.durationChanged)
    self.mediaPlayer.positionChanged.connect(self.positionChanged)
    self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
    # ...
def getSnapshot(self):
    image = self.snapshotVideoSurface.current_frame
    if not image.isNull():
        image.save("TestFileName", "jpg")

upwoted 答案对我有用,但一段时间后我找到了另一种方法来解决所描述的问题。我希望它能帮助别人。我使用 QGraphicsVideoItem 而不是 QAbstractVideoSurface 实现。它帮助我摆脱了冗余的 QAbstractVideoSurface 实现并简化了代码。 这是播放器创建:

def initUi(self):
    # Create widget to display video frames
    self.graphicsView = QGraphicsView()
    self.scene = QGraphicsScene(self, self.graphicsView)
    self.videoItem = QGraphicsVideoItem()
    self.graphicsView.setScene(self.scene)
    self.graphicsView.scene().addItem(self.videoItem)

    # Create media player
    self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
    self.mediaPlayer.setVideoOutput(self.videoItem)
    self.mediaPlayer.durationChanged.connect(self.mediaPlayerDurationChanged)
    self.mediaPlayer.positionChanged.connect(self.mediaPlayerPositionChanged)
    self.mediaPlayer.stateChanged.connect(self.mediaPlayerStateChanged)
    #...

快照是这样完成的:

    def getSnapshot(self):
    snapshot = self.graphicsView.grab()

另一种适合我的选择。它有助于避免实现 QAbstractVideoSurface 并保持源清洁。

QPixmap.grabWindow(self.videoWidget.winId()).save("Test4", 'jpg')