1

我创建了一个简单的媒体播放器。我想添加对显示的视频进行快照的功能。为此,我使用“self.videoWidget.grab()”函数。但似乎grab() 不能正常工作,因为我得到了一张作为wiget 背景颜色的图片,而不是快照。如果我将 'self.videoWidget.grab()' 替换为 'snapshot = self.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) 

如何抓取窗口

窗口中实际发生了什么

4

3 回答 3

3

一种可能的解决方案是实现QAbstractVideoSurface显示最后一帧的 a:

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")
于 2021-04-13T21:22:18.580 回答
1

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()
于 2021-07-12T16:54:12.073 回答
0

另一种对我有用的选择。它有助于避免实施 QAbstractVideoSurface 并保持源代码清洁。

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

于 2021-07-31T17:23:17.877 回答