3.2.8增加录屏功能

This commit is contained in:
g1879 2023-03-03 19:49:39 +08:00
parent c000bec826
commit 9ce271561f
6 changed files with 119 additions and 7 deletions

View File

@ -3,9 +3,11 @@
@Author : g1879 @Author : g1879
@Contact : g1879@qq.com @Contact : g1879@qq.com
""" """
from base64 import b64decode
from json import loads, JSONDecodeError from json import loads, JSONDecodeError
from os import sep
from pathlib import Path from pathlib import Path
from time import perf_counter, sleep from time import perf_counter, sleep, time
from warnings import warn from warnings import warn
from requests import Session from requests import Session
@ -15,7 +17,7 @@ from .chromium_driver import ChromiumDriver
from .chromium_element import ChromiumScroll, ChromiumElement, run_js, make_chromium_ele, ChromiumElementWaiter from .chromium_element import ChromiumScroll, ChromiumElement, run_js, make_chromium_ele, ChromiumElementWaiter
from .commons.constants import HANDLE_ALERT_METHOD, ERROR, NoneElement from .commons.constants import HANDLE_ALERT_METHOD, ERROR, NoneElement
from .commons.locator import get_loc from .commons.locator import get_loc
from .commons.tools import get_usable_path from .commons.tools import get_usable_path, clean_folder
from .commons.web import cookies_to_tuple from .commons.web import cookies_to_tuple
from .errors import ContextLossError, ElementLossError, AlertExistsError, CallMethodError, TabClosedError, \ from .errors import ContextLossError, ElementLossError, AlertExistsError, CallMethodError, TabClosedError, \
NoRectError, BrowserConnectError NoRectError, BrowserConnectError
@ -37,6 +39,7 @@ class ChromiumBase(BasePage):
self._debug_recorder = None self._debug_recorder = None
self._tab_obj = None self._tab_obj = None
self._set = None self._set = None
self._screencast = None
self._set_start_options(address, None) self._set_start_options(address, None)
self._set_runtime_settings() self._set_runtime_settings()
@ -324,6 +327,13 @@ class ChromiumBase(BasePage):
self._set = ChromiumBaseSetter(self) self._set = ChromiumBaseSetter(self)
return self._set return self._set
@property
def screencast(self):
"""返回用于录屏的对象"""
if self._screencast is None:
self._screencast = Screencast(self)
return self._screencast
def run_cdp(self, cmd, **cmd_args): def run_cdp(self, cmd, **cmd_args):
"""执行Chrome DevTools Protocol语句 """执行Chrome DevTools Protocol语句
:param cmd: 协议项目 :param cmd: 协议项目
@ -803,6 +813,89 @@ class ChromiumBase(BasePage):
return self.set.load_strategy return self.set.load_strategy
class Screencast(object):
def __init__(self, page):
self._page = page
self._path = None
self._quality = 100
def start(self, save_path=None, quality=None):
"""开始录屏
:param save_path: 录屏保存位置
:param quality: 录屏质量
:return: None
"""
self.set(save_path, quality)
if self._path is None:
raise ValueError('save_path必须设置。')
if not self._path.isascii():
raise TypeError('仅支持英文路径。')
clean_folder(self._path)
self._page.driver.Page.screencastFrame = self._onScreencastFrame
self._page.run_cdp('Page.startScreencast', everyNthFrame=1, quality=self._quality)
def stop(self, to_mp4=True, video_name=None):
"""停止录屏
:param to_mp4: 是否合并成MP4格式
:param video_name: 视频文件名为None时以当前时间名命
:return: 文件路径
"""
self._page.driver.Page.screencastFrame = None
self._page.run_cdp('Page.stopScreencast')
if not to_mp4:
return str(Path(self._path).absolute())
if not str(video_name).isascii():
raise TypeError('仅支持英文文件名。')
try:
from cv2 import VideoWriter, imread
from numpy import fromfile, uint8
except ModuleNotFoundError:
raise ModuleNotFoundError('请先安装cv2pip install opencv-python')
pic_list = Path(self._path).glob('*.jpg')
img = imread(str(next(pic_list)))
imgInfo = img.shape
size = (imgInfo[1], imgInfo[0])
if video_name and not video_name.endswith('mp4'):
video_name = f'{video_name}.mp4'
name = f'{time()}.mp4' if not video_name else video_name
fourcc = 14
videoWrite = VideoWriter(f'{self._path}{sep}{name}', fourcc, 8, size)
for i in pic_list:
img = imread(str(i))
videoWrite.write(img)
clean_folder(self._path, ignore=(name,))
return f'{self._path}{sep}{name}'
def set(self, save_path=None, quality=None):
"""设置录屏参数
:param save_path: 保存路径
:param quality: 视频质量可取值0-100
:return:
"""
if save_path:
save_path = Path(save_path)
if save_path.exists() and save_path.is_file():
raise TypeError('save_path必须指定文件夹。')
save_path.mkdir(parents=True, exist_ok=True)
self._path = str(save_path)
if quality is not None:
if quality < 0 or quality > 100:
raise ValueError('quality必须在0-100之间。')
self._quality = quality
def _onScreencastFrame(self, **kwargs):
with open(f'{self._path}\\{kwargs["metadata"]["timestamp"]}.jpg', 'wb') as f:
f.write(b64decode(kwargs['data']))
self._page.run_cdp('Page.screencastFrameAck', sessionId=kwargs['sessionId'])
class ChromiumBaseSetter(object): class ChromiumBaseSetter(object):
def __init__(self, page): def __init__(self, page):
self._page = page self._page = page

View File

@ -40,6 +40,7 @@ class ChromiumBase(BasePage):
self._upload_list: list = ... self._upload_list: list = ...
self._wait: ChromiumBaseWaiter = ... self._wait: ChromiumBaseWaiter = ...
self._set: ChromiumBaseSetter = ... self._set: ChromiumBaseSetter = ...
self._screencast: Screencast = ...
def _connect_browser(self, tab_id: str = None) -> None: ... def _connect_browser(self, tab_id: str = None) -> None: ...
@ -118,6 +119,9 @@ class ChromiumBase(BasePage):
@property @property
def set(self) -> ChromiumBaseSetter: ... def set(self) -> ChromiumBaseSetter: ...
@property
def screencast(self) -> Screencast: ...
def run_js(self, script: str, *args: Any, as_expr: bool = False) -> Any: ... def run_js(self, script: str, *args: Any, as_expr: bool = False) -> Any: ...
def run_js_loaded(self, script: str, *args: Any, as_expr: bool = False) -> Any: ... def run_js_loaded(self, script: str, *args: Any, as_expr: bool = False) -> Any: ...
@ -193,6 +197,21 @@ class ChromiumBase(BasePage):
timeout: float = None) -> Union[bool, None]: ... timeout: float = None) -> Union[bool, None]: ...
class Screencast(object):
def __init__(self, page: ChromiumBase):
self._page: ChromiumBase = ...
self._path: str = ...
self._quality: int = ...
def start(self, save_path: Union[str, Path] = None, quality: int = None) -> None: ...
def stop(self, to_mp4: bool = True, video_name: str = None) -> str: ...
def set(self, save_path: Union[str, Path] = None, quality: int = None) -> None: ...
def _onScreencastFrame(self, **kwargs) -> None: ...
class ChromiumBaseWaiter(object): class ChromiumBaseWaiter(object):
def __init__(self, page: ChromiumBase): def __init__(self, page: ChromiumBase):
self._driver: ChromiumBase = ... self._driver: ChromiumBase = ...

View File

@ -149,9 +149,8 @@ class ChromiumDriver(object):
if event['method'] in self.event_handlers: if event['method'] in self.event_handlers:
try: try:
self.event_handlers[event['method']](**event['params']) self.event_handlers[event['method']](**event['params'])
except Exception: except Exception as e:
pass raise RuntimeError(f"\n回调函数 {self.event_handlers[event['method']].__name__} 错误:\n{e}")
# raise RuntimeError(f"回调函数 {event['method']} 错误:{e}")
self.event_queue.task_done() self.event_queue.task_done()

View File

@ -25,7 +25,7 @@ def get_long(txt) -> int: ...
def port_is_using(ip: str, port: Union[str, int]) -> bool: ... def port_is_using(ip: str, port: Union[str, int]) -> bool: ...
def clean_folder(folder_path: Union[str, Path], ignore: list = None) -> None: ... def clean_folder(folder_path: Union[str, Path], ignore: Union[tuple, list] = None) -> None: ...
def unzip(zip_path: str, to_path: str) -> Union[list, None]: ... def unzip(zip_path: str, to_path: str) -> Union[list, None]: ...

View File

@ -47,6 +47,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
self._response = None self._response = None
self._download_set = None self._download_set = None
self._set = None self._set = None
self._screencast = None
self._set_start_options(driver_or_options, session_or_options) self._set_start_options(driver_or_options, session_or_options)
self._set_runtime_settings() self._set_runtime_settings()

View File

@ -6,7 +6,7 @@ with open("README.md", "r", encoding='utf-8') as fh:
setup( setup(
name="DrissionPage", name="DrissionPage",
version="3.2.7", version="3.2.8",
author="g1879", author="g1879",
author_email="g1879@qq.com", author_email="g1879@qq.com",
description="Python based web automation tool. It can control the browser and send and receive data packets.", description="Python based web automation tool. It can control the browser and send and receive data packets.",