Pyqt在给定时间线上显示视频缩略图
我有一个简单的pyqt视频播放器,但我想让视频时间轴的背景显示视频中那个时刻发生的画面。我需要用什么方法才能高效地实现这个呢?下面是我基本的播放器代码:
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_())
我尝试过不同的方法,比如使用QVideoFrame,然后用QImage把它转换成图片,但我不太确定这样用是否正确。
这是我想要的时间轴效果的例子:
1 个回答
0
你可以使用 QVideoSink,通过 videoFrame()
这个函数来获取来自 QtMultimedia
的帧数据。另一种方法是使用 cv2
(Open CV)来创建缩略图。
完整代码:
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()