ffpyplayer+Qt,制作一个视频播放器

ffpyplayer+Qt,制作一个视频播放器

  • 项目地址
  • FFmpegMediaPlayer
  • VideoWidget

项目地址

https://gitee.com/chiyaun/QtFFMediaPlayer

FFmpegMediaPlayer

按照 QMediaPlayer的方法重写一个ffpyplayer

# coding:utf-8
import logging
from typing import Unionfrom PySide6.QtCore import QTimer, QUrl, Signal, QObject
from PySide6.QtGui import QImage
from PySide6.QtMultimedia import QMediaPlayer
from PySide6.QtWidgets import QWidget
from ffpyplayer.pic import Image
from ffpyplayer.player import MediaPlayerlogging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger('FFmpegMediaPlayer')class FFmpegMediaPlayer(QObject):"""ffmpeg media player"""sourceChanged = Signal(QUrl)mediaStatusChanged = Signal(QMediaPlayer.MediaStatus)positionChanged = Signal(int)durationChanged = Signal(int)metaDataChanged = Signal(dict)playbackStateChanged = Signal(QMediaPlayer.PlaybackState)playingChanged = Signal(bool)errorChanged = Signal(QMediaPlayer.Error)def __init__(self, parent=None):super().__init__(parent)self.__source: QUrl = QUrl()self.__playerWidget: QWidget = Noneself.__mediaStatus: QMediaPlayer.MediaStatus = QMediaPlayer.MediaStatus.NoMediaself.__position: int = 0self.__duration: int = 0self.__metaData: dict = {}self.__error: QMediaPlayer.Error = QMediaPlayer.Error.NoErrorself.__errorString: str = ''self.timer = QTimer(self)self.player: MediaPlayer = Noneself.timer.timeout.connect(self._update_frame)def setSource(self, source: Union[str, QUrl]):if isinstance(source, QUrl):source = source.toString()if self.player:self.player.close_player()self.timer.stop()self.player = Nonelogger.debug(f'set source: {source}')self.player = MediaPlayer(source,ff_opts={'paused': True,'autoexit': True,'vn': False,'sn': False,'aud': 'sdl'},loglevel='debug',callback=self.__callback)self.__source = QUrl(source)self.sourceChanged.emit(self.__source)def source(self) -> QUrl:return self.__sourcedef fps(self) -> float:fps = self.metadata()["frame_rate"][0] / self.metadata()["frame_rate"][1]return fpsdef close(self):self.player.close_player()logger.debug('player closed')def play(self):self.player.set_pause(False)self.timer.start()self.playingChanged.emit(True)self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.PlayingState)logger.debug('player playing')def pause(self):self.player.set_pause(True)self.timer.stop()self.playingChanged.emit(False)self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.PausedState)logger.debug('player paused')def stop(self):self.player.set_pause(True)self.timer.stop()self.playingChanged.emit(False)self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.StoppedState)logger.debug('player stopped')def toggle(self):logger.debug('toggle player')self.player.toggle_pause()if self.isPaused():self.timer.stop()self.playingChanged.emit(False)self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.PausedState)logger.debug('player paused')else:self.timer.start()self.playingChanged.emit(True)self.playbackStateChanged.emit(QMediaPlayer.PlaybackState.PlayingState)logger.debug('player playing')def isPlaying(self) -> bool:return not self.player.get_pause()def isPaused(self) -> bool:return self.player.get_pause()def setPosition(self, position: int):if self.player is None:returnlogger.debug(f'set position: {position}')self.player.seek(position, relative=False)def position(self) -> int:return self.player.get_pts()def duration(self) -> int:return int(self.metadata().get('duration', 0))def __setPosition(self, position: Union[float, int]):if self.player is None:returnposition = int(position)if self.__position == position:returnself.__position = positionself.positionChanged.emit(position)def metaData(self) -> dict:meta = self.player.get_metadata()if meta != self.__metaData:self.__metaData = metaself.metaDataChanged.emit(meta)return metadef setVolume(self, volume: int):if self.player is None:returnlogger.debug(f'set volume: {volume}')self.player.set_volume(volume / 100)def volume(self) -> int:return int(self.player.get_volume() * 100)def setMuted(self, muted: bool):if self.player is None:returnlogger.debug(f'set muted: {muted}')self.player.set_mute(muted)def isMuted(self) -> bool:return self.player.get_mute()def setOutputPixFormat(self, pix_fmt: str):self.player.set_output_pix_fmt(pix_fmt)def outputPixFormat(self) -> str:return self.player.get_output_pix_fmt()def metadata(self) -> dict:return self.player.get_metadata()def __setMediaStatus(self, status: QMediaPlayer.MediaStatus):if status == self.__mediaStatus:returnlogger.debug(f'set media status: {status}')self.__mediaStatus = statusself.mediaStatusChanged.emit(status)def mediaStatus(self) -> QMediaPlayer.MediaStatus:return self.__mediaStatusdef _update_frame(self):frame, val = self.player.get_frame()if frame is None:self.__setMediaStatus(QMediaPlayer.MediaStatus.LoadingMedia)if val == 'eof':# 结束状态处理self.__setMediaStatus(QMediaPlayer.MediaStatus.EndOfMedia)self.stop()returnif not frame:returnself.__setMediaStatus(QMediaPlayer.MediaStatus.LoadedMedia)img: Imagetm: intimg, tm = frameinterval = round(1000 / self.fps())if self.timer.interval() != interval:logger.debug(f'set timer interval: {interval}')self.timer.setInterval(interval)w, h = img.get_size()self.__setPosition(tm)if self.__duration != self.duration():self.durationChanged.emit(self.duration())self.metaData()qimage = QImage(img.to_bytearray(True)[0], w, h, QImage.Format.Format_RGB888)self.__playerWidget.setImage(qimage)def setVideoOutput(self, widget: QWidget):self.__playerWidget = widgetlogger.debug(f'set video output: {widget}')if not hasattr(widget, 'setImage'):logger.error('视频输出小部件必须有 `setImage` 方法')raise ValueError('视频输出小部件必须有 `setImage` 方法')def errorString(self) -> str:return self.__errorStringdef __setError(self, error: QMediaPlayer.Error):if self.__error == error:returnself.__error = errorself.errorChanged.emit(error)def error(self) -> QMediaPlayer.Error:return self.__errordef __callback(self, *args, **kwargs):tp, status = args[0].split(':')if tp == 'read':if status == 'error':self.__errorString = '资源读取错误'self.__setMediaStatus(QMediaPlayer.MediaStatus.InvalidMedia)self.__setError(QMediaPlayer.Error.ResourceError)self.stop()self.close()elif status == 'exit':self.__errorString = '播放结束'self.__setMediaStatus(QMediaPlayer.MediaStatus.EndOfMedia)self.stop()self.close()elif tp == 'audio':if status == 'error':self.__errorString = '音频播放错误'self.__setError(QMediaPlayer.Error.ResourceError)self.stop()self.close()elif status == 'exit':self.__errorString = '音频播放结束'self.stop()self.close()elif tp == 'video':if status == 'error':self.__errorString = '视频播放错误'self.__setError(QMediaPlayer.Error.ResourceError)self.stop()self.close()elif status == 'exit':self.__errorString = '视频播放结束'self.stop()self.close()

VideoWidget

# coding:utf-8
from typing import Unionfrom PySide6.QtCore import QRect, Qt, Signal, Property
from PySide6.QtGui import QImage, QPainter, QPixmap, QColor, QPainterPath, QKeyEvent
from PySide6.QtWidgets import QWidgetclass VideoWidget(QWidget):"""视频播放控件, 该控件只能作为子页面使用, 不能单独使用"""imageChanged = Signal(QImage)fullScreenChanged = Signal(bool)_topLeftRadius = 0_topRightRadius = 0_bottomLeftRadius = 0_bottomRightRadius = 0def __init__(self, parent=None):super().__init__(parent)self._transparent = Falseself._backgroundColor = Qt.GlobalColor.blackself.image = QImage()self.backgroundImage = QImage()self.setBorderRadius(5, 5, 5, 5)self.setMouseTracking(True)def setPixmap(self, pixmap: QPixmap):""" 设置显示的图像 """self.setImage(pixmap)def pixmap(self) -> QPixmap:""" 获取显示的图像 """return QPixmap.fromImage(self.image)def setImage(self, image: Union[QPixmap, QImage] = None):""" 设置显示的图像 """self.image = image or QImage()if isinstance(image, QPixmap):self.image = image.toImage()self.imageChanged.emit(self.image)self.update()def setBackgroundImage(self, image: Union[str, QPixmap, QImage] = None):""" 设置背景图像 """self.backgroundImage = image or QImage()if isinstance(image, QPixmap):self.backgroundImage = image.toImage()self.update()elif isinstance(image, str):self.backgroundImage.load(image)self.update()def backgroundImage(self) -> QImage:""" 获取背景图像 """return self.backgroundImagedef isNull(self):return self.image.isNull()def setTransparent(self, transparent: bool):""" 设置是否透明 """self._transparent = transparentself.update()def isTransparent(self) -> bool:""" 获取是否透明 """return self._transparentdef setBackgroundColor(self, color: QColor):""" 设置背景颜色 """self._backgroundColor = colorself.update()def backgroundColor(self) -> QColor:""" 获取背景颜色 """return self._backgroundColordef setBorderRadius(self, topLeft: int, topRight: int, bottomLeft: int, bottomRight: int):""" set the border radius of image """self._topLeftRadius = topLeftself._topRightRadius = topRightself._bottomLeftRadius = bottomLeftself._bottomRightRadius = bottomRightself.update()@Property(int)def topLeftRadius(self):return self._topLeftRadius@topLeftRadius.setterdef topLeftRadius(self, radius: int):self.setBorderRadius(radius, self.topRightRadius, self.bottomLeftRadius, self.bottomRightRadius)@Property(int)def topRightRadius(self):return self._topRightRadius@topRightRadius.setterdef topRightRadius(self, radius: int):self.setBorderRadius(self.topLeftRadius, radius, self.bottomLeftRadius, self.bottomRightRadius)@Property(int)def bottomLeftRadius(self):return self._bottomLeftRadius@bottomLeftRadius.setterdef bottomLeftRadius(self, radius: int):self.setBorderRadius(self.topLeftRadius, self.topRightRadius, radius, self.bottomRightRadius)@Property(int)def bottomRightRadius(self):return self._bottomRightRadius@bottomRightRadius.setterdef bottomRightRadius(self, radius: int):self.setBorderRadius(self.topLeftRadius,self.topRightRadius,self.bottomLeftRadius,radius)def paintEvent(self, event):painter = QPainter(self)painter.setRenderHints(QPainter.RenderHint.Antialiasing)painter.setRenderHint(QPainter.RenderHint.LosslessImageRendering)path = QPainterPath()w, h = self.width(), self.height()# top linepath.moveTo(self.topLeftRadius, 0)path.lineTo(w - self.topRightRadius, 0)# top right arcd = self.topRightRadius * 2path.arcTo(w - d, 0, d, d, 90, -90)# right linepath.lineTo(w, h - self.bottomRightRadius)# bottom right arcd = self.bottomRightRadius * 2path.arcTo(w - d, h - d, d, d, 0, -90)# bottom linepath.lineTo(self.bottomLeftRadius, h)# bottom left arcd = self.bottomLeftRadius * 2path.arcTo(0, h - d, d, d, -90, -90)# left linepath.lineTo(0, self.topLeftRadius)# top left arcd = self.topLeftRadius * 2path.arcTo(0, 0, d, d, -180, -90)# 裁剪路径painter.setPen(Qt.PenStyle.NoPen)painter.setClipPath(path)if not self._transparent:painter.fillRect(self.rect(), self._backgroundColor)  # 填充颜色if not self.backgroundImage.isNull():painter.drawImage(self.rect(), self.backgroundImage)  # 填充背景图片if self.isNull():return# draw imageimage = self.image# 保持宽高比居中显示image_ratio = image.width() / image.height()widget_ratio = self.width() / self.height()# 计算适配后的显示区域if widget_ratio > image_ratio:target_width = self.height() * image_ratiotarget_rect = QRect((self.width() - target_width) // 2, 0,target_width, self.height())else:target_height = self.width() / image_ratiotarget_rect = QRect(0, (self.height() - target_height) // 2,self.width(), target_height)painter.drawImage(target_rect, image)def fullScreen(self):""" 全屏显示 """self.setWindowFlags(Qt.WindowType.Window)self.showFullScreen()self.fullScreenChanged.emit(True)def normalScreen(self):""" 退出全屏显示 """self.setWindowFlags(Qt.WindowType.SubWindow)self.showNormal()self.fullScreenChanged.emit(False)def toggleFullScreen(self):""" 切换全屏显示 """if self.isFullScreen():self.normalScreen()else:self.fullScreen()self.setBorderRadius(0, 0, 0, 0)def setFullScreen(self, fullScreen: bool):""" 设置全屏显示 """if fullScreen:self.fullScreen()else:self.normalScreen()def keyPressEvent(self, event: QKeyEvent):""" 键盘按下事件 """if event.key() == Qt.Key.Key_Escape:self.toggleFullScreen()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/76289.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 国际化配置项详解

Spring Boot 国际化配置项详解 1. 核心配置项分类 将配置项分为以下类别,便于快速定位: 1.1 消息源配置(MessageSource 相关) 控制属性文件的加载、编码、缓存等行为。 配置项作用默认值示例说明spring.messages.basename指定属…

拍摄的婚庆视频有些DAT的视频文件打不开怎么办

3-12 现在的婚庆公司大多提供结婚的拍摄服务,或者有一些第三方公司做这方面业务,对于视频拍摄来说,有时候会遇到这样一种问题,就是拍摄下来的视频文件,然后会有一两个视频文件是损坏的,播放不了&#xff0…

【力扣hot100题】(073)数组中的第K个最大元素

花了两天时间搞明白答案的快速排序和堆排序。 两种都写了一遍&#xff0c;感觉堆排序更简单很多。 两种都记录一下&#xff0c;包括具体方法和易错点。 快速排序 class Solution { public:vector<int> nums;int quicksort(int left,int right,int k){if(leftright) r…

【亲测】Linux 使用 Matplotlib 显示中文

文章目录 安装中文字体在Matplotlib中使用该字体来显示中文 在 Linux 系统中使用 Matplotlib 绘制图表时&#xff0c;如果需要显示中文&#xff0c;可能会遇到中文字符显示为方块或者乱码的问题。这是因为Matplotlib 默认使用的字体不支持中文。本文手把手带你解决这个问题。 …

Redis Java 客户端 之 SpringDataRedis

SpringDataRedis SpringData是Spring中数据操作的模块&#xff0c;包含对各种数据库的集成&#xff0c;其中对Redis集成模块就叫做SpringDataRedis&#xff0c; 官方地址&#xff1a;https://spring.io/projects/spring-data-redis 特性&#xff1a; 提供了对不同Redis客户端…

数字化转型:重构生存逻辑,不止系统升级

数字化转型不过是升级系统&#xff0c;砸了钱、耗了力&#xff0c;却没达到预期&#xff0c;竞争力也没提升。实际上&#xff0c;数字化转型是对企业生存逻辑的彻~底重构&#xff0c;关乎商业模式、运营流程等方方面面。​ 很多企业觉得数字化转型是 IT 部门的事&#xff0c;只…

C语言队列的实现

目录 ​编辑 &#xff08;一&#xff09;队列的定义,初始化及创建结点 &#xff08;二&#xff09;入队和出队&#xff0c;以及取队头队尾的数据 (三)销毁队列 队列是指只允许在一端进行插入数据操作&#xff0c;在另⼀端进行删除数据操作的特殊线性表&#xff0c;队列具有先…

mapbox进阶,使用本地dem数据,加载hillshade山体阴影图层

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️hillshade 山体阴影图层 api1.3.1 ☘️…

量子纠错码实战:从Shor码到表面码

引言&#xff1a;量子纠错的必要性 量子比特的脆弱性导致其易受退相干和噪声影响&#xff0c;单量子门错误率通常在10⁻~10⁻量级。量子纠错码&#xff08;QEC&#xff09;通过冗余编码测量校正的机制&#xff0c;将逻辑量子比特的错误率降低到可容忍水平。本文从首个量子纠错…

10. git switch

基本概述 git switch是 Git 2.23 版本之后新增的命令&#xff0c;专门用于切换分支&#xff0c;目的是替代 git checkout 中与分支操作相关的功能&#xff0c;使命令语义更清晰、更安全。 基本用法 1.切换到已有分支 git switch <branch-name>常用选项 1.从当前分支…

LeetCode 热题 100 堆

215. 数组中的第K个最大元素 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 **k** 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 …

PIXOR:基于LiDAR的3D检测模型解析

目录 1、前言 2、PIXOR介绍 2.1. 什么是PIXOR&#xff1f; 2.2. PIXOR如何工作&#xff1f; 3、表现和应用 3.1、PIXOR的性能表现 3.2、PIXOR的应用场景 3.3、PIXOR的局限性与挑战 4. PIXOR的未来展望 5. 结语 1、前言 自动驾驶技术正以前所未有的速度发展&#xff…

Vue中权限控制的方案

文章目录 源码&#xff1a;一、页面级1.1、路由守卫1.2、动态路由 二、按钮级别2.1、通过v-if来判断2.2、通过组件包裹的方式来判断2.3、通过自定义指令的方式 三、接口级别 源码&#xff1a; https://gitee.com/liu-qiang-yyds/sysPermission 一、页面级 1.1、路由守卫 前端…

【OSG学习笔记】Day 1: OSG初探——环境搭建与第一个3D窗口

什么是 OSG&#xff1f; 全称&#xff1a;OpenSceneGraph&#xff08;开源场景图&#xff09; 定位&#xff1a;一个基于 C/OpenGL 的高性能开源3D图形开发工具包&#xff0c;专注于实时渲染和复杂场景管理。 核心思想&#xff1a;通过 场景图&#xff08;Scene Graph&#xf…

Kubernetes 入门篇之网络插件 calico 部署与安装

在运行kubeadm init 和 join 命令部署好master和node节点后&#xff0c;kubectl get nodes 看到节点都是NotReady状态&#xff0c;这是因为没有安装CNI网络插件。 kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Not…

游戏开发中 C#、Python 和 C++ 的比较

&#x1f3ac; Verdure陌矣&#xff1a;个人主页 &#x1f389; 个人专栏: 《C/C》 | 《转载or娱乐》 &#x1f33e; 种完麦子往南走&#xff0c; 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持&#xff01;❤️ 摘要&#xff1a; 那么哪种编程语言最适合游戏开发…

LabVIEW真空度监测与控制系统

开发了一种基于LabVIEW的真空度信号采集与管理系统&#xff0c;该系统通过图形化编程语言实现了真空度的高精度测量和控制。利用LabVIEW的强大功能&#xff0c;研制了相应的硬件并设计了完整的软件解决方案&#xff0c;以满足工业应用中对真空度监测的精确要求。 项目背景 随着…

checkra1n越狱出现的USB error -10问题解决

使用checkra1n进行越狱是出现&#xff1a; 解决办法(使用命令行进行越狱)&#xff1a; 1. cd /Applications/checkra1n.app/Contents/MacOS 2. ./checkra1n -cv 3. 先进入恢复模式 a .可使用爱思助手 b. 或者长按home,出现关机的滑条&#xff0c;同时按住home和电源键&#…

spring boot 中 WebClient 与 RestTemplate 的对比总结

以下是 WebClient 与 RestTemplate 的对比总结&#xff0c;以纯文本表格形式呈现&#xff1a; 核心特性对比 特性RestTemplateWebClient线程模型同步阻塞&#xff1a;每个请求占用线程&#xff0c;直到响应返回。异步非阻塞&#xff1a;基于事件循环&#xff0c;高效处理高并发…

深入浅出SPI通信协议与STM32实战应用(W25Q128驱动)(实战部分)

1. W25Q128简介 W25Q128 是Winbond推出的128M-bit&#xff08;16MB&#xff09;SPI接口Flash存储器&#xff0c;支持标准SPI、Dual-SPI和Quad-SPI模式。关键特性&#xff1a; 工作电压&#xff1a;2.7V~3.6V分页结构&#xff1a;256页/块&#xff0c;每块16KB&#xff0c;共1…