I have a basic pyqt video player but I want the video time line background to be pictures of what is happing at that time of the video, what will I have to use to do this efficiently? Here is the basic player:
import sys
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtMultimediaWidgets import QVideoWidget
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
class PyVideoPlayer(QWidget):
def __init__(self, parent=None):
super(PyVideoPlayer, self).__init__(parent)
self.mediaPlayer = QMediaPlayer()
self.audioOutput = QAudioOutput()
videoWidget = QVideoWidget()
self.playButton = QPushButton()
self.playButton.setIcon(QIcon("gui/images/svg_icons/icon_play.svg"))
self.playButton.setEnabled(False)
self.playButton.clicked.connect(self.play)
self.volumeButton = QPushButton()
self.volumeButton.setIcon(QIcon("gui/images/svg_icons/icon_volume.svg"))
self.volumeButton.clicked.connect(self.showVolumeSlider)
self.volumeSlider = QSlider(orientation=Qt.Horizontal)
self.volumeSlider.setRange(0, 100)
self.volumeSlider.setValue(self.audioOutput.volume() * 100)
self.volumeSlider.setMaximumWidth(100)
self.volumeSlider.sliderMoved.connect(self.setVolume)
self.volumeSlider.hide()
self.positionSlider = QSlider(orientation=Qt.Horizontal)
self.positionSlider.setRange(0, 0)
self.positionSlider.sliderMoved.connect(self.setPosition)
self.timeLabel = QLabel()
self.timeLabel.setStyleSheet("color: #4f5b6e; font-family: 'Roboto'; font-size: 9pt; font-weight: bold;")
# Set up the layout
videoLayout = QVBoxLayout()
videoLayout.addWidget(videoWidget, stretch=1)
controlLayout = QHBoxLayout()
controlLayout.addWidget(self.playButton)
controlLayout.addWidget(self.volumeButton)
controlLayout.addWidget(self.volumeSlider)
controlLayout.addWidget(self.timeLabel)
layout = QVBoxLayout()
layout.addLayout(videoLayout)
layout.addWidget(self.positionSlider)
layout.addLayout(controlLayout)
layout.setContentsMargins(10, 0, 10, 0)
self.setLayout(layout)
self.mediaPlayer.setVideoOutput(videoWidget)
self.mediaPlayer.playbackStateChanged.connect(self.mediaStateChanged)
self.mediaPlayer.positionChanged.connect(self.positionChanged)
self.mediaPlayer.durationChanged.connect(self.durationChanged)
self.mediaPlayer.errorOccurred.connect(self.handleError)
def setMedia(self, fileName):
self.mediaPlayer.setSource(QUrl.fromLocalFile(fileName))
self.mediaPlayer.setAudioOutput(self.audioOutput)
self.playButton.setEnabled(True)
self.play()
def play(self):
if self.mediaPlayer.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
self.mediaPlayer.pause()
else:
self.mediaPlayer.play()
def mediaStateChanged(self, state):
if state == QMediaPlayer.PlaybackState.PlayingState:
self.playButton.setIcon(QIcon("gui/images/svg_icons/icon_pause.svg"))
else:
self.playButton.setIcon(QIcon("gui/images/svg_icons/icon_play.svg"))
def positionChanged(self, position):
self.positionSlider.setValue(position)
self.timeLabel.setText(f"{position // 60000:02}:{(position // 1000) % 60:02} / {self.mediaPlayer.duration() // 60000:02}:{(self.mediaPlayer.duration() // 1000) % 60:02}")
def durationChanged(self, duration):
self.positionSlider.setRange(0, duration)
def setPosition(self, position):
self.mediaPlayer.setPosition(position)
def setVolume(self, volume):
self.audioOutput.setVolume(volume / 100)
def showVolumeSlider(self):
if self.volumeSlider.isHidden():
self.volumeSlider.show()
else:
self.volumeSlider.hide()
def handleError(self):
self.playButton.setEnabled(False)
print("Error: " + self.mediaPlayer.errorString())
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = PyVideoPlayer()
ex.setMedia("your_video.mp4") # Replace with your video file path
ex.show()
sys.exit(app.exec_())
I have tried different things such as using QVideoFrame and then converting to an image using QImage but unsure if its supposed to be used like this.
This is an example of how I want the time line to look:
You can do it with QVideoSink with the videoFrame()
function to get frame data from QtMultimedia
or in another way you can use cv2
(Open CV) to create thumbnails.
full code:
import sys
import cv2
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtMultimediaWidgets import QVideoWidget
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
class TimeLineImageWidget(QWidget):
def __init__(self):
super(TimeLineImageWidget, self).__init__()
self.image = QLabel()
self.lay = QVBoxLayout()
self.resize(200, 250)
self.setStyleSheet("background: white;")
self.lay.addWidget(self.image)
self.setLayout(self.lay)
self.show()
def setImage(self, img):
pixmap = QPixmap(img)
self.image.setPixmap(
pixmap.scaled(200, 150, Qt.AspectRatioMode.KeepAspectRatio)
)
def destry(self):
self.image.close()
class PyVideoPlayer(QWidget):
def __init__(self, parent=None):
super(PyVideoPlayer, self).__init__(parent)
self.mediaPlayer = QMediaPlayer()
self.audioOutput = QAudioOutput()
videoWidget = QVideoWidget()
self.thumbnail_widget = QWidget()
self.thumbnail = QHBoxLayout()
self.thumbnail_widget.setLayout(self.thumbnail)
self.playButton = QPushButton()
self.playButton.setIcon(QIcon("gui/images/svg_icons/icon_play.svg"))
self.playButton.setEnabled(False)
self.playButton.clicked.connect(self.play)
self.volumeButton = QPushButton()
self.volumeButton.setIcon(QIcon("gui/images/svg_icons/icon_volume.svg"))
self.volumeButton.clicked.connect(self.showVolumeSlider)
self.volumeSlider = QSlider(orientation=Qt.Orientation.Horizontal)
self.volumeSlider.setRange(0, 100)
self.volumeSlider.setValue(int(self.audioOutput.volume() * 100))
self.volumeSlider.setMaximumWidth(100)
self.volumeSlider.sliderMoved.connect(self.setVolume)
self.volumeSlider.hide()
self.positionSlider = QSlider(orientation=Qt.Orientation.Horizontal)
self.positionSlider.setRange(0, 0)
self.positionSlider.sliderMoved.connect(self.setPosition)
self.timeLabel = QLabel()
self.timeLabel.setStyleSheet(
"color: #4f5b6e; font-family: 'Roboto'; font-size: 9pt; font-weight: bold;"
)
self.timeLineImage = TimeLineImageWidget()
# Set up the layout
videoLayout = QVBoxLayout()
videoLayout.addWidget(videoWidget, stretch=1)
controlLayout = QHBoxLayout()
controlLayout.addWidget(self.playButton)
controlLayout.addWidget(self.volumeButton)
controlLayout.addWidget(self.volumeSlider)
controlLayout.addWidget(self.timeLabel)
layout = QVBoxLayout()
layout.addLayout(videoLayout)
layout.addWidget(self.timeLineImage)
layout.addWidget(self.positionSlider)
layout.addWidget(self.thumbnail_widget)
layout.addLayout(controlLayout)
layout.setContentsMargins(10, 0, 10, 0)
self.setLayout(layout)
self.mediaPlayer.setVideoOutput(videoWidget)
self.mediaPlayer.playbackStateChanged.connect(self.mediaStateChanged)
self.mediaPlayer.positionChanged.connect(self.positionChanged)
self.mediaPlayer.durationChanged.connect(self.durationChanged)
self.mediaPlayer.errorOccurred.connect(self.handleError)
def setMedia(self, fileName):
self.generate_thumbnail_previews(fileName)
self.mediaPlayer.setSource(QUrl.fromLocalFile(fileName))
self.mediaPlayer.setAudioOutput(self.audioOutput)
self.playButton.setEnabled(True)
self.play()
def generate_thumbnail_previews(self, url):
video_capture = cv2.VideoCapture(url)
total_frames = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
interval = total_frames // 10 # Generate 10 thumbnails
thumbnails = []
for i in range(10):
video_capture.set(cv2.CAP_PROP_POS_FRAMES, i * interval)
ret, frame = video_capture.read()
if ret:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # Convert frame to RGB
height, width, _ = frame.shape
img = QPixmap.fromImage(
QImage(frame.data, width, height, width * 3, QImage.Format_RGB888)
)
thumbnails.append(img)
video_capture.release()
# Display thumbnails on the slider
for i, thumbnail in enumerate(thumbnails):
label = QLabel()
label.setPixmap(thumbnail.scaled(100, 100, Qt.KeepAspectRatio))
self.thumbnail.addWidget(label)
def play(self):
if self.mediaPlayer.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
self.mediaPlayer.pause()
else:
self.mediaPlayer.play()
def mediaStateChanged(self, state):
if state == QMediaPlayer.PlaybackState.PlayingState:
self.playButton.setIcon(QIcon("gui/images/svg_icons/icon_pause.svg"))
else:
self.playButton.setIcon(QIcon("gui/images/svg_icons/icon_play.svg"))
def positionChanged(self, position):
self.positionSlider.setValue(position)
self.timeLabel.setText(
f"{position // 60000:02}:{(position // 1000) % 60:02} / {self.mediaPlayer.duration() // 60000:02}:{(self.mediaPlayer.duration() // 1000) % 60:02}"
)
# update image timeline
if self.mediaPlayer.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
sink = self.mediaPlayer.videoSink()
self.timeLineImage.setImage(sink.videoFrame().toImage())
def durationChanged(self, duration):
self.positionSlider.setRange(0, duration)
def setPosition(self, position):
self.mediaPlayer.setPosition(position)
# update image timeline
if self.mediaPlayer.playbackState() != QMediaPlayer.PlaybackState.PlayingState:
sink = self.mediaPlayer.videoSink()
self.timeLineImage.setImage(sink.videoFrame().toImage())
def setVolume(self, volume):
self.audioOutput.setVolume(volume / 100)
def showVolumeSlider(self):
if self.volumeSlider.isHidden():
self.volumeSlider.show()
else:
self.volumeSlider.hide()
def handleError(self):
self.playButton.setEnabled(False)
print("Error: " + self.mediaPlayer.errorString())
if __name__ == "__main__":
try:
app = QApplication(sys.argv)
ex = PyVideoPlayer()
ex.resize(720, 480)
ex.setMedia("video file.mp4") # Replace with your video file path
ex.show()
sys.exit(app.exec())
except KeyboardInterrupt:
exit()