实现一个读取本地视频的窗口。要求用同一个按钮完成读取视频与结束视频的功能,另外用同一个按钮完成播放与暂停的功能。
这两个按钮代表的4个功能在逻辑上需要理清,我认为以下的三个问题需要考虑到。
问题1:在视频播放途中(按钮2的值为关闭),先点击暂停(按钮2的值变为继续),再点击关闭,此时整个界面是什么现象?
答:错误的现象是:暂停操作把timer_camera给stop,导致在点击按钮1时,程序误认为视频已经被关闭,需要重新导入文件,并且两个按钮的值都不会被更新,导致再次导入视频进行播放时,按钮2的值显示为“继续”。根本原因就是没有分清timer_camera是因为视频被关闭而stop,还是因为暂停动作而stop。这是很严重的逻辑错误。 正确的现象是:暂停后关闭,首先界面会清空,其次两个按钮的值都会重置。即按钮1的值变为“选择上传”,按钮2的值变为“暂停”
解决办法:增加一个signal信号,初始为0,在暂停后赋值为1,在Video函数判断timer_camera是否isActive时顺便检查这个信号,只有timer_camera关闭并且signal=0时才能去读取本地文件,否则就将signal赋值为0,然后再对整个界面重置
问题2:在未选择视频时点击暂停,暂停是否无效?按钮2的值会更新吗?
答:无效,不会更新,即点击后任何反应都没有。 解决方法:在刚进入Video_Pause函数时判断一下是否有视频在播放,有视频在播放时才进行后续操作
问题3:播放完成后按钮1的值是什么? 窗口里显示什么?
答:按钮1的值显示“播放结束”,窗口里显示视频最后一帧。再次点击按钮1后,按钮1的值变为“选择上传”,窗口清空 解决方法:在刚进入Video_Play函数时判断是否结束,结束时把按钮1的值从“关闭”更新为“播放结束”,对界面不做处理,即停在最后一帧。另外,在Video函数的刚开始位置,设置窗口clear语句,清空窗口,这样就能保证当前按钮1不论是“选择上传”状态,还是“播放结束状态”,还是“关闭”状态,只要一点击,窗口内容就会清空,合情合理。
关于视频画面的自适应问题
- 直接读取的话,显示多大区域的视频画面取决于你的label标签有多大。比如视频尺寸是800x600,而label大小是600x600,那么有一部分的画面被切割掉,无法显示
- 使用 self.view.setScaledContents(True) , 自适应。将视频进行形变,使之尺寸与label相同,可以达到画面完全显示的效果,但是会产生形变,扭曲
- 获取视频的尺寸,然后resize界面中label的大小,使二者尺寸相同。这样也可以实现画面完全展示的目的,但视频可能会过长或过宽,以至于超出界面的边界,这时候调整一下边界就好了,所以在这种方法下最好不要用 self.setFixedSize 。 另外,如果label的四周有别的控件,视频尺寸又过大时,可能会影响整体的布局
from PyQt5.QtWidgets import QApplication, QWidget,QMessageBox,QFileDialog
from PyQt5.uic import loadUi
from PyQt5 import QtCore,QtGui
import cv2 as cv2
import sys
class MyMainForm(QWidget):
def __init__(self):
super().__init__()
loadUi("Frame.ui", self) #加载UI文件
# self.setFixedSize(550,550) # 固定大小不可放缩
self.slot_init() #初始化槽函数
'''初始化所有槽函数'''
def slot_init(self):
# 初始化变量
self.cap = cv2.VideoCapture() #视频流
self.timer_camera = QtCore.QTimer() #定义定时器,用于控制显示视频的帧率
self.signal=0 # 用于解决问题1。区分timer_camera是因为视频被关闭而stop,还是因为暂停动作而stop。
# 初始化函数
self.upload.clicked.connect(self.Video)
self.timer_camera.timeout.connect(self.Video_Play)
self.pause.clicked.connect(self.Video_Pause)
def Video(self):
self.view.clear() # 用于解决问题3 。每次想选择新视频时,显示界面都清空一下
if self.timer_camera.isActive() == False and self.signal==0:
selectFileName,_ = QFileDialog.getOpenFileName(self,'选择文件','./')
if selectFileName == '':
QMessageBox.information(self,"提示","请选择文件",QMessageBox.Yes)
elif(selectFileName.lower().endswith(('.mp4', '.avi','flv'))==False):
QMessageBox.warning(self,'警告','请输入正确的视频格式',QMessageBox.Yes)
else:
# # 使画面完全展示 , 但是无法控制视频展示画面的尺寸
# mp4 = cv2.VideoCapture(selectFileName)
# width = mp4.get(cv2.CAP_PROP_FRAME_WIDTH) # float
# height = mp4.get(cv2.CAP_PROP_FRAME_HEIGHT) # float
# self.view.resize(int(width),int(height))
flag = self.cap.open(selectFileName)
if flag: # 检测视频是否被打开
self.timer_camera.start(30)
self.upload.setText('关闭')
else:
self.signal=0
self.pause.setText('暂停')
self.timer_camera.stop()
self.cap.release()
#self.view.clear() # 如果加上这一句,就会在播放完毕时自动清空显示界面
self.upload.setText('选择上传')
def Video_Play(self):
flag,self.image = self.cap.read() #从视频流中读取.当flag为False时,表示视频位于最后一帧。
if flag: # 用于解决问题3 。 检测视频是否播放完毕
show = cv2.cvtColor(self.image,cv2.COLOR_BGR2RGB) #视频色彩转换回RGB,这样才是现实的颜色
img = QtGui.QImage(show.data,show.shape[1],show.shape[0],QtGui.QImage.Format_RGB888) #把读取到的视频数据变成QImage形式
self.view.setPixmap(QtGui.QPixmap.fromImage(img)) #往显示视频的Label里 显示QImage
self.view.setScaledContents(True) # 使画面完全展示+比例自适应。 保证能够显示视频画面里得所有内容,但是会变形、扭曲
else:
self.upload.setText('播放完毕')
def Video_Pause(self):
flag,self.image = self.cap.read()
if flag: # 用于解决问题2 。保证只有在视频被选中且播放时,这个按钮被按时才有效果
if self.timer_camera.isActive() == False:
self.timer_camera.start(30)
self.pause.setText('暂停')
else:
self.signal=1
self.timer_camera.stop()
self.pause.setText('继续')
if __name__=='__main__':
app=QApplication(sys.argv)
w=MyMainForm()
w.show()
sys.exit(app.exec_())
相应的ui文件为
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>980</width>
<height>832</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="view">
<property name="geometry">
<rect>
<x>50</x>
<y>160</y>
<width>881</width>
<height>611</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="QPushButton" name="upload">
<property name="geometry">
<rect>
<x>200</x>
<y>80</y>
<width>93</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>选择上传</string>
</property>
</widget>
<widget class="QPushButton" name="pause">
<property name="geometry">
<rect>
<x>660</x>
<y>80</y>
<width>93</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>暂停</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
效果为
值得一提的是,OpenCV中使用VideoCapture类读的视频是没有音频的,OpenCV只是一个图像库。如果要进一步处理音频则需要用到一个库——MoviePy,这个库是Python视频编辑库,可裁剪、拼接、标题插入、视频合成、视频处理和自定义效果。如果想在显示画面的时候播放音频,可以用多线程的方法,两个任务同时开启。但是如果视频播放后续支持进度条拖动或者倍速,这种方案又不行了,想要更改音频播放的速度有点困难。