本文使用python 语言,使用 Pyqt5+opencv 实现视频播放。包括:开始/停止播放、网页播放、逐帧播放、倍速播放、播放进度条、抓拍、下载、局部放大等。
核心代码
#加载视频 启动定时器 暂停播放时停止定时器即可
self.cap = cv2.VideoCapture(self.url)
self.fps=self.cap.get(5)
self.timer_camera = QTimer(self)
self.timer_camera.timeout.connect(self.show_pic)
#显示视频
def show_pic(self):
#暂停
if self.bfC == 5:
return
success, frame = self.cap.read()
if success:
self.img=frame
show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
# showImage=showImage.copy(self.x1, self.y1,self.x2, self.y2)
pix=QPixmap.fromImage(showImage)
pix = pix.scaled(self.label.width(), self.label.height(), Qt.IgnoreAspectRatio)
#--------------------------------------
#局部放大
pix = pix.copy(self.x1, self.y1,self.x2, self.y2)
self.label.setPixmap(pix)
#进度条
self.jdt()
cv2.waitKey(int(1000/self.fps))
else:
self.img = ""
self.pushButton_2.setText("重播")
self.timer_camera.stop()
当需要播放多个视频时 使用线程播放
# 显示视频
def show_pic(self,ids,rtmpUrl):
pp = 1
#加载多次视频【当加载视频流/网络视频时 有可能一次加载不上】
while pp < 4:
cap = cv2.VideoCapture(rtmpUrl)
fps = int(cap.get(cv2.CAP_PROP_FPS))
print("-----------------------1111111", fps)
if fps == 0:
pp += 1
continue
break
if fps == 0:
print("视频流获取失败=====》》",rtmpUrl)
# QMessageBox.information(self, "提示框", "读取视频失败,请查看网络连接!", QMessageBox.Yes)
return
aa={"id":ids,"cap":cap}
#将加载的视频对象存入集合中
self.splList.append(aa)
#播放窗口
label = self.monitorList[ids]["monitorObject"]
#值为1 表示该窗口正在播放视频 停止播放视频时改变改值即可
self.monitorList[ids]["previewHandler"]=1
# 暂停
while True:
if self.monitorList[ids]["previewHandler"]==1:
try:
success, frame = cap.read()
if success:
show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
pix = QPixmap.fromImage(showImage)
pix = pix.scaled(label.width(),label.height(), Qt.IgnoreAspectRatio)
#全屏下才可局部放大
if label.isFullScreen():
pix = pix.copy(self.x1, self.y1, self.x2, self.y2)
label.setPixmap(pix)
cv2.waitKey(1)
else:
cap.release()
self.splList.remove(aa)
self.monitorList[ids]["previewHandler"] = 0
print("opencv 读取失败===========>", self.splList)
return
except Exception as e:
cap.release()
self.splList.remove(aa)
self.monitorList[ids]["previewHandler"] = 0
print("读取cap异常======》》",e)
return
else:
cap.release()
self.splList.remove(aa)
self.monitorList[ids]["previewHandler"] = 0
print("停止播放======》》")
return
#启动 python 服务
os.system("python -m http.server 8083")
#打开html
webbrowser.open_new("http://localhost:8081/SSBF.html?sl=l&url=%s" % (self.url))
其中包含了播放本地视频以及摄像机视频。选用object标签 浏览器支持IE11
<html>
<head>
<meta charset="utf-8">
<title>Object 播放视频title>
<script type="text/javascript" src="jquery-3.2.1.min.js">script>
head>
<body id="dsb">
body>
<script type="text/javascript">
function jscs() {
// alert(0);
var bb = "";
var bb2 = "";
bb += ";
bb2 += "";
$("#dsb").html(bb);
// $("#pp").oncanplay;
}
function aa() {
// alert("------------------jhjkhjk");
// alert($("#pp").height());
$("#pp").width(800);
$("#pp").height(800);
// alert($("#pp").height());
}
function load() {
var url = document.location.toString();//获取url地址
var urlParmStr = url.slice(url.indexOf('?')+1);//获取问号后所有的字符串
var arr = urlParmStr.split('&');//通过&符号将字符串分割转成数组
var sl=arr[0].split("=")[1];
if (sl=="s"){
var ip=arr[1].split("=")[1];
var account=arr[2].split("=")[1];
var pasword=arr[3].split("=")[1];
var brand=arr[4].split("=")[1];
}
else if(sl=="l"){
var url=arr[1].split("=")[1];
}
// alert(ip);
// alert(account);
// alert(pasword);
// alert(brand);
var html=""
if (sl=="s"){
if (brand == "h"){
html=html+""
}
else if (brand == "d") {
html=html+""
}
}
else if(sl =="l"){
html=html+"
}
$("#dsb").html(html)
}
window.onload=function(){
// alert("onload");
// jscs();
load();
aa();
}
script>
html>
实现简单 就是获取当前帧 加1 或减1。self.pos的值详见进度条
#逐帧播放
def zzbfShow(self):
try:
success, frame = self.cap.read()
if success:
show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
# 图片自适应控件尺寸
self.label.setScaledContents(True)
self.label.setPixmap(QPixmap.fromImage(showImage))
self.jdt()
else:
self.pushButton_2.setText("重播")
self.timer_camera.stop()
except Exception as e:
print("eeeee==============>",e)
#上一帧
def syz(self):
self.pos -=1
self.horizontalSlider.setValue(self.pos)
self.horizontalSlider.value()
self.cap.set(1,self.pos)
self.zzbfShow()
#下一帧
def xyz(self):
self.pos += 1
self.horizontalSlider.setValue(self.pos)
self.horizontalSlider.value()
self.cap.set(1, self.pos)
self.zzbfShow()
通过改变 self.fps 的值,使得在播放方法中 cv2.waitKey(int(1000/self.fps)) 等待时间改变控制播放速度。
#倍速播放
def bsbf(self):
name = self.comboBox.currentText()
if name == "正常":
self.fps=self.cap.get(5)
elif name == "2倍":
self.fps=self.cap.get(5)*2
elif name == "0.5倍":
self.fps = int(self.cap.get(5) / 2)
else:
pass
自定义进度条,重写点击事件。否则 Pyqt5 进度条在单击时 会在进度条上前后移动,无法实现点那里就播放那里的视频。
from PyQt5.QtWidgets import QSlider
from PyQt5.QtCore import Qt,pyqtSignal
class MySlide(QSlider):
clicked = pyqtSignal(object)
def __init__(self, parent=None):
super(MySlide, self).__init__(parent)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
# if event.button() == Qt.RightButton:
super().mousePressEvent(event) # 调用父级的单击事件,听说这样能不影响进度条原来的拖动
val_por = event.pos().x() / self.width() # 获取鼠标在进度条的相对位置
self.setValue(int(val_por * self.maximum())) # 改变进度条的值
self.clicked.emit(self.value()) # 点击发送信号,这里可不要
# self.clicked.emit(self.rect)
#进度条拖拽事件
self.horizontalSlider.sliderMoved.connect(self.movedSlide)
#进度条释放事件
self.horizontalSlider.sliderReleased.connect(self.releasedSlide)
#进度条点击事件
self.horizontalSlider.clicked.connect(self.onClickSlide)
#设置进度条的值 self.count为视频总帧数
self.count=self.cap.get(7)
self.horizontalSlider.setMinimum(0)
self.horizontalSlider.setMaximum(self.count)
# 单击滑块
def onClickSlide(self):
self.pos = self.horizontalSlider.value()
self.cap.set(1, self.pos)
# 拖动滑块
def movedSlide(self):
self.bfC=5
# 释放滑块
def releasedSlide(self):
self.bfC=2
self.pos = self.horizontalSlider.value()
self.cap.set(1, self.pos)
#进度条
def jdt(self):
if self.bfC == 5:
return
d=int(self.pos/self.fps)
a = datetime.timedelta(seconds=d)
b = str(a).split(":")
c = b[1] + ":" + b[2]
self.label_2.setText(c)
# 设置进度条的值
if self.loop_flag == self.pos:
self.loop_flag = self.loop_flag + 2
self.horizontalSlider.setValue(self.loop_flag)
else:
self.pos = self.horizontalSlider.value()
self.loop_flag = self.pos
#抓拍
def zp(self):
stratTime = datetime.datetime.now().strftime('%Y-%m-%d%H%M%S')
path = "D://DCXT//img//"
try:
if self.img !="":
path+=stratTime+".jpg"
cv2.imwrite(path, self.img)
QMessageBox.information(self, "提示框", "抓拍成功", QMessageBox.Yes)
except Exception as e:
print("抓拍异常=======>",e)
QMessageBox.information(self, "提示框", "抓拍失败", QMessageBox.Yes)
#线程下载视频
class downloadThread(QThread):
add_item = pyqtSignal(str)
show_time = pyqtSignal(str)
def __init__(self, surl,url,*args, **kwargs):
super(downloadThread, self).__init__(*args, **kwargs)
print('视频下载地址surl==',surl)
print('视频地址url==',url)
self.surl = surl
self.vurl=url
self.needStop = False
def run(self, *args, **kwargs):
self.savefile(self.surl,self.vurl)
# 文件下载 视频下载地址、视频地址
def savefile(self,saveflieurl,vurl):
download_url =vurl
#视频的保存地址+名称
path = os.path.join(saveflieurl, os.path.basename(download_url))
print('thread => save',path)
# 缓存下载
try:
#打开一个远程的url连接
response = urllib.request.urlopen(download_url)
filesize = response.headers['content-length']
print('self.filesize=====', filesize)
except Exception as e:
print('e2====', e)
try:
r = requests.get(download_url, stream=True)
f = open(path, "wb")
offset = 0
# chunk是指定每次写入的大小,每次只写了512byte
for chunk in r.iter_content(chunk_size=512):
if chunk and not self.needStop:
f.seek(offset)
f.write(chunk)
offset = offset + len(chunk)
proess = offset / int(filesize) * 100
print('proess=====', proess)
# 发送添加信号
self.add_item.emit(str(proess))
#time.sleep(0.5)
else:
break
f.close()
except Exception as e:
print('e=====', e)
def stopdownload(self):
self.needStop = True
#下载
def downVideo(self):
if self.downstateAll >= 2:
if self.pushButton.text() == '下载':
self.pushButton.setText('取消')
# 声明线程实例
self.downloadthread = downloadThread(self.saveflieurl, self.url)
# 绑定增加控件函数
self.downloadthread.add_item.connect(self.download_progress)
self.downloadthread.start()
else:
self.downloadthread.stopdownload()
# 删除文件
filePath = self.saveflieurl + '\\' + os.path.basename(self.url)
if os.path.isfile(filePath):
print('删除文件的路径是===', filePath)
os.remove(filePath)
print('删除文件成功')
else:
pass
self.downloadthread.quit()
self.downloadthread.wait()
self.pushButton.setText('下载')
else:
QMessageBox.information(self, "提示", "没有权限下载!", QMessageBox.Yes)
#下载进度控件
def download_progress(self,progress):
self.progressBar.setValue(float(progress))
if((float(progress))>=100):
QMessageBox.information(self, '信息', '下载成功!')
self.pushButton.setText('下载')
else:
pass
重写 QLabel 实现可在label上绘画出要局部放大的区域,将坐标返回。坐标返回后要乘以 根据视频的分辨率以及播放窗口的比例值。
目前这种实现局部放大的方法就是复制所选矩形区域的图片 放到label 中,自适应显示。
另一种就是将视频图片放大一定的倍数,然后在偏移到自己的播放控件label中。
如果你有更好的方法可以评论区讲下【虚心求教】。
# 图片自适应控件尺寸
self.label.setScaledContents(True)
from PyQt5.QtCore import Qt,QRect,pyqtSignal
from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QPainter,QPen,QMouseEvent
# from videoPlayTest import MyVideoPlay
class MyLabel(QLabel):
clicked = pyqtSignal(object)
doubleClick=pyqtSignal(str)
# sendmsg.connect(MyVideoPlay.MySlot)
def __init__(self, parent=None):
super(MyLabel, self).__init__(parent)
self.x0 = 0
self.y0 = 0
self.x1 = 0
self.y1 = 0
self.pos0 = None
self.pos1 = None
self.flag = False
self.isShow=False
self.rect=""
#绘制矩形
def paintEvent(self, event):
super().paintEvent(event)
self.rect = QRect(self.x0, self.y0, self.x1 - self.x0, self.y1 - self.y0)
painter = QPainter(self)
painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
if self.isShow == True:
painter.drawRect(self.rect)
# print("绘制矩形=========>",self.rect)
#获取起始点
def mousePressEvent(self, event):
QLabel.mousePressEvent(self, event)
self.flag = True
self.pos0 = event.globalPos()
self.x0 = event.x()
self.y0 = event.y()
# print("获取起始点======>",self.x0,self.y0)
#鼠标移动
def mouseMoveEvent(self, event):
QLabel.mouseMoveEvent(self, event)
if self.flag:
self.pos1 = event.globalPos()
self.x1 = event.x()
self.y1 = event.y()
self.update()
self.isShow = True
# print("鼠标移动=====>",self.x1, self.y1)
#鼠标释放
def mouseReleaseEvent(self, event):
QLabel.mouseReleaseEvent(self, event)
self.x0 = 0
self.y0 = 0
self.x1 = 0
self.y1 = 0
self.pos0 = None
self.pos1 = None
self.flag = False
self.isShow = False
print("鼠标释放=====>",self.rect)
self.clicked.emit(self.rect)
# 返回绝对坐标
def getRectGlobalPos(self):
poses = [(self.pos0.x(), self.pos0.y())]
poses.append((self.pos1.x(), self.pos1.y()))
print("返回绝对坐标=====>",poses)
return poses
def mouseDoubleClickEvent(self,QMouseEvent):
self.doubleClick.emit("double clicked")
#接收局部放大信号
def MySlot(self,rect):
print("接收局部放大信号=====>",rect,rect.x(),rect.y())
if self.label.isFullScreen():
self.x1 = rect.x()
self.y1 = rect.y()
self.x2 = rect.width()
self.y2 = rect.height()
else:
self.x1=rect.x()*0.78
self.y1=rect.y()*0.78
self.x2=rect.width()*0.78
self.y2=rect.height()*0.78
视频播放窗口可以放大,但变小时缩不回来。这个问题让我找了一两天,,真是。。。。。。。
有两点:
1.使用布局不可以默认 Stretch 为0。应这样使用。
self.gridLayout_4.setColumnStretch(0, 1)
self.gridLayout_4.setColumnStretch(1, 1)
self.gridLayout_4.setColumnStretch(2, 1)
self.gridLayout_4.setColumnStretch(3, 1)
self.gridLayout_4.setRowStretch(0, 1)
self.gridLayout_4.setRowStretch(1, 1)
self.gridLayout_4.setRowStretch(2, 1)
self.gridLayout_4.setRowStretch(3, 1)
在用到单多画面切换时【比如我用的4、9、16画面切换】
def show4(self):
self.hm = 4
self.gridLayout_4.setColumnStretch(0, 1)
self.gridLayout_4.setColumnStretch(1, 1)
self.gridLayout_4.setColumnStretch(2, 0)
self.gridLayout_4.setColumnStretch(3, 0)
self.gridLayout_4.setRowStretch(0, 1)
self.gridLayout_4.setRowStretch(1, 1)
self.gridLayout_4.setRowStretch(2, 0)
self.gridLayout_4.setRowStretch(3, 0)
self.selectedMonitor = -1
self.fubin2.append(0)
self.fubin2.append(0)
self.DispLb_1.show()
self.DispLb_2.show()
self.DispLb_3.hide()
self.DispLb_4.hide()
self.DispLb_5.show()
self.DispLb_6.show()
self.DispLb_7.hide()
self.DispLb_8.hide()
self.DispLb_9.hide()
self.DispLb_10.hide()
self.DispLb_11.hide()
self.DispLb_12.hide()
self.DispLb_13.hide()
self.DispLb_14.hide()
self.DispLb_15.hide()
self.DispLb_16.hide()
def show9(self):
self.hm = 9
self.selectedMonitor = -1
self.fubin2.append(0)
self.fubin2.append(0)
self.gridLayout_4.setColumnStretch(0, 1)
self.gridLayout_4.setColumnStretch(1, 1)
self.gridLayout_4.setColumnStretch(2, 1)
self.gridLayout_4.setColumnStretch(3, 0)
self.gridLayout_4.setRowStretch(0, 1)
self.gridLayout_4.setRowStretch(1, 1)
self.gridLayout_4.setRowStretch(2, 1)
self.gridLayout_4.setRowStretch(3, 0)
self.DispLb_1.show()
self.DispLb_2.show()
self.DispLb_3.show()
self.DispLb_4.hide()
self.DispLb_5.show()
self.DispLb_6.show()
self.DispLb_7.show()
self.DispLb_8.hide()
self.DispLb_9.show()
self.DispLb_10.show()
self.DispLb_11.show()
self.DispLb_12.hide()
self.DispLb_13.hide()
self.DispLb_14.hide()
self.DispLb_15.hide()
self.DispLb_16.hide()
def show16(self):
self.hm = 16
self.gridLayout_4.setColumnStretch(0, 1)
self.gridLayout_4.setColumnStretch(1, 1)
self.gridLayout_4.setColumnStretch(2, 1)
self.gridLayout_4.setColumnStretch(3, 1)
self.gridLayout_4.setRowStretch(0, 1)
self.gridLayout_4.setRowStretch(1, 1)
self.gridLayout_4.setRowStretch(2, 1)
self.gridLayout_4.setRowStretch(3, 1)
self.selectedMonitor = -1
self.fubin2.append(0)
self.fubin2.append(0)
self.DispLb_1.show()
self.DispLb_2.show()
self.DispLb_3.show()
self.DispLb_4.show()
self.DispLb_5.show()
self.DispLb_6.show()
self.DispLb_7.show()
self.DispLb_8.show()
self.DispLb_9.show()
self.DispLb_10.show()
self.DispLb_11.show()
self.DispLb_12.show()
self.DispLb_13.show()
self.DispLb_14.show()
self.DispLb_15.show()
self.DispLb_16.show()
2.必须要给控件设置最小值。【不知道为什么,有懂的大神请赐教】
self.label.setMinimumSize(QtCore.QSize(10, 10))
希望能对你有帮助。
欢迎指教,共同进步!!!