I've created a simple media player. I want to add the ability to make a snapshots of the displayed video. For this purpose I use 'self.videoWidget.grab()' function. But seems like grab() doesn't work properly for that aim because instead of snapshot I got a picture colored as a wiget background. If I substitute 'self.videoWidget.grab()' with 'snapshot = self.grab()' I got the snapshot of the widget but without videoWidget content on it (pictures are added). I went throw similar questions but found nothing. I'm a newbie with PyQt5 so I hope the solution is obvious, but I failed to find it alone.
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)
How window is grabed
What actually is going on in window
A possible solution is to implement a QAbstractVideoSurface
that has the last frame shown:
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
Then
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")