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.pswp.cn/bicheng/76289.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/76289.shtml
英文地址,請注明出處:http://en.pswp.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…