PyQt5-读取本地视频播放与暂停

505 阅读5分钟

实现一个读取本地视频的窗口。要求用同一个按钮完成读取视频与结束视频的功能,另外用同一个按钮完成播放与暂停的功能。

 

这两个按钮代表的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不论是“选择上传”状态,还是“播放结束状态”,还是“关闭”状态,只要一点击,窗口内容就会清空,合情合理。

 

 

关于视频画面的自适应问题
  1. 直接读取的话,显示多大区域的视频画面取决于你的label标签有多大。比如视频尺寸是800x600,而label大小是600x600,那么有一部分的画面被切割掉,无法显示
  2. 使用 self.view.setScaledContents(True) , 自适应。将视频进行形变,使之尺寸与label相同,可以达到画面完全显示的效果,但是会产生形变,扭曲
  3. 获取视频的尺寸,然后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视频编辑库,可裁剪、拼接、标题插入、视频合成、视频处理和自定义效果。如果想在显示画面的时候播放音频,可以用多线程的方法,两个任务同时开启。但是如果视频播放后续支持进度条拖动或者倍速,这种方案又不行了,想要更改音频播放的速度有点困难。