From 27acc7205540d3e20d1e7fb64a829b0d30bebc0f Mon Sep 17 00:00:00 2001 From: liubiren Date: Mon, 29 Dec 2025 21:44:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=B8=B8=E6=9B=B4=E6=96=B0=20from=20N?= =?UTF-8?q?UC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 剪映脚本生成自动化/draft.py | 544 ++++++++++++++++++++++++++++++ 剪映脚本生成自动化/edgetts.py | 75 +++++ 剪映脚本生成自动化/main.py | 615 +--------------------------------- 票据理赔自动化/masterdata.py | 76 +++-- 4 files changed, 677 insertions(+), 633 deletions(-) create mode 100644 剪映脚本生成自动化/draft.py create mode 100644 剪映脚本生成自动化/edgetts.py diff --git a/剪映脚本生成自动化/draft.py b/剪映脚本生成自动化/draft.py new file mode 100644 index 0000000..7118c6e --- /dev/null +++ b/剪映脚本生成自动化/draft.py @@ -0,0 +1,544 @@ +# -*- coding: utf-8 -*- + +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +import pycapcut as capcut +from pycapcut import trange +from pycapcut.keyframe import KeyframeProperty +from pycapcut.segment import ClipSettings + +from edgetts import EdgeTTS + + +class GenerateDraft: + """剪映脚本生成器""" + + # noinspection PyShadowingNames + def __init__( + self, + name: str, + video_width: int = 1080, + video_height: int = 1920, + video_fps: int = 30, + drafts_path: str = r"C:\Users\admin\AppData\Local\JianyingPro\User Data\Projects\com.lveditor.draft", + materials_path: str = r"C:\Users\admin\PycharmProjects\Python\剪映脚本生成自动化\materials", + ): + """ + 初始化生成剪映脚本 + :param name: 草稿名称 + :param video_width: 视频宽度 + :param video_height: 视频高度 + :param video_fps: 视频帧率 + :param drafts_path: 剪映草稿文件夹路径 + :param materials_path: 素材文件夹路径 + """ + print("正在初始化剪映脚本生成器...", end="") + # noinspection PyBroadException + try: + # 草稿名称 + self.name = name + # 视频宽度和高度 + self.video_width, self.video_height = video_width, video_height + # 视频帧率 + self.video_fps = video_fps + # 剪映草稿文件夹路径 + self.drafts_path = drafts_path + # 素材文件夹路径 + self.materials_path = Path(materials_path) # 转为path对象 + # 检查素材文件夹是否存在,若不存在则抛出异常 + if not self.materials_path.exists(): + raise FileNotFoundError(f"素材文件夹不存在") + + # 初始化草稿文件夹管理器 + self.draft_folder = capcut.DraftFolder(self.drafts_path) + + # 新建草稿 + self.draft = self.draft_folder.create_draft( + draft_name=self.name, + width=self.video_width, + height=self.video_height, + fps=self.video_fps, + allow_replace=True, # 允许覆盖 + ) + + # 脚本持续时长 + self.duration = 0 + # 实例化EdgeTTS + self.synthesizer = EdgeTTS(self.materials_path) + print("已完成") + except Exception as exception: + print(f"发生异常:{str(exception)}") + raise + + def _get_material(self, name: str) -> str: + """ + 获取素材 + :param name: 素材名称 + :return 素材路径 + """ + # 素材路径 + material_path = self.materials_path / name + if not material_path.exists(): + raise FileNotFoundError(f"素材文件不存在") + return material_path.as_posix() + + def _add_audio( + self, + track_name: str, + name: str, + target_timerange: Tuple[Optional[int, str], Optional[int, str]], + source_timerange: Optional[Tuple[str, str]] = None, + speed: Optional[float] = None, + volume: float = 1.0, + fade: Optional[Tuple[str, str]] = None, + ) -> None: + """ + 添加音频片段 + :param track_name: 轨道名称 + :param name: 音频素材名称 + :param target_timerange: 音频素材在轨道上的范围,包括开始时间和持续时长 + :param source_timerange: 截取音频素材范围,包括开始时间和持续时长,默认根据音频素材开始时间根据播放速度截取与音频素材持续时长等长的部分 + :param speed: 播放速度 + :param volume: 播放音量 + :param fade: 淡入淡出设置 + :return: 无 + """ + try: + # 构建音频片段 + audio_segment = capcut.AudioSegment( + material=self._get_material(name), + target_timerange=trange(*target_timerange), + source_timerange=( + trange(*source_timerange) if source_timerange else None + ), + speed=speed, + volume=volume, + ) + # 添加淡入淡出效果 + if fade: + audio_segment.add_fade(*fade) + + # 向指定轨道添加音频片段 + self.draft.add_segment(segment=audio_segment, track_name=track_name) + return + except Exception: + raise + + def _add_video( + self, + track_name: str, + name: str, + target_timerange: Tuple[Optional[int, str], Optional[int, str]], + source_timerange: Optional[ + Tuple[Optional[int, str], Optional[int, str]], + ] = None, + speed: Optional[float] = None, + volume: float = 1.0, + clip_settings: Optional[Dict[str, Any]] = None, + keyframes: Optional[List[Tuple[KeyframeProperty, str, float]]] = None, + animation: Optional[Dict[str, Any]] = None, + transition: Optional[Dict[str, Any]] = None, + background_filling: Optional[Tuple[str, Any]] = None, + ) -> None: + """ + 添加视频/图片片段 + :param track_name: 轨道名称 + :param name: 视频/图片素材名称 + :param target_timerange: 视频素材在轨道上的范围,包括开始时间和持续时长 + :param source_timerange: 截取视频素材范围,包括开始时间和持续时长 + :param speed: 播放速度 + :param volume: 播放音量 + :param clip_settings: 图像调节设置 + :param keyframes: 关键帧设置 + :param animation: 动画设置 + :param transition: 转场设置 + :param background_filling: 背景填充设置 + :param track_name: 轨道名称 + :return: 无 + """ + try: + # 构建视频/图片片段 + video_segment = capcut.VideoSegment( + material=self._get_material(name), + target_timerange=trange(*target_timerange), + source_timerange=( + trange(*source_timerange) if source_timerange else None + ), + speed=speed, + volume=volume, + clip_settings=ClipSettings(**clip_settings) if clip_settings else None, + ) + # (视频素材)添加关键帧 + if keyframes: + # noinspection PyShadowingBuiltins + for property, time, value in keyframes: + video_segment.add_keyframe(property, time, value) + + # (视频素材)添加动画 + if animation: + video_segment.add_animation(**animation) + + # (视频素材)添加转场 + if transition: + video_segment.add_transition(**transition) + + # (图片素材)添加背景填充 + if background_filling: + video_segment.add_background_filling(*background_filling) + + # 向指定轨道添加视频/图片片段 + self.draft.add_segment(segment=video_segment, track_name=track_name) + return + except Exception: + raise + + def _add_text( + self, + track_name: str, + content: str, + timerange: Tuple[Optional[int, str], Optional[int, str]], + border: Optional[Dict[str, Any]] = None, + background: Optional[Dict[str, Any]] = None, + font: Optional[str] = None, + style: Optional[Dict[str, Any]] = None, + clip_settings: Optional[Dict[str, Any]] = None, + bubble: Optional[Dict[str, Any]] = None, + effect: Optional[Dict[str, Any]] = None, + animation: Optional[Dict[str, Any]] = None, + ) -> None: + """ + 添加文本片段 + :param track_name: 轨道名称 + :param content: 文本内容 + :param timerange: 文本素材在轨道上的范围,包括开始时间和持续时长 + :param border: 文本描边设置 + :param background: 文本背景设置 + :param font: 字体类型 + :param style: 字体样式 + :param clip_settings: 文本调节设置 + :param bubble: 气泡设置 + :param effect: 花字设置 + :param animation: 动画设置 + :return: 无 + """ + try: + # 构建文本片段 + text_segment = capcut.TextSegment( + text=content, + timerange=trange(*timerange), + border=capcut.TextBorder(**border) if border else None, + background=capcut.TextBackground(**background) if background else None, + font=capcut.FontType(font) if font else None, + style=capcut.TextStyle(**style) if style else None, + clip_settings=( + capcut.ClipSettings(**clip_settings) if clip_settings else None + ), + ) + # 添加气泡 + if bubble: + text_segment.add_bubble(**bubble) + + # 添加花字 + if effect: + text_segment.add_effect( + **effect + ) # 可先将花字保存预设,再在C:/Users/admin/AppData/Local/JianyingPro/User Data/Presets/Text_V2/预设文本?.textpreset获取花字resource_id + + # 添加动画 + if animation: + text_segment.add_animation(**animation) + + # 向指定轨道添加文本片段 + self.draft.add_segment(segment=text_segment, track_name=track_name) + return + except Exception: + raise + + def _add_sticker( + self, + track_name: str, + resource_id: str, + target_timerange: Tuple[Optional[int, str], Optional[int, str]], + clip_settings: Optional[Dict[str, Any]] = None, + ) -> None: + """ + 添加贴纸片段 + :param track_name: 轨道名称 + :param resource_id: 贴纸 resource_id + :param target_timerange: 贴纸在轨道上的范围,包括开始时间和持续时长 + :param clip_settings: 文本调节设置 + :return: 无 + """ + try: + # 构建贴纸 + sticker_segment = capcut.StickerSegment( + resource_id=resource_id, # 可先将贴纸保存为我的预设,再在C:\Users\admin\AppData\Local\JianyingPro\User Data\Presets\Combination\Presets\我的预设?\preset_draft获取 + target_timerange=trange(*target_timerange), + clip_settings=( + capcut.ClipSettings(**clip_settings) if clip_settings else None + ), + ) + + # 向指定轨道添加贴纸 + self.draft.add_segment(segment=sticker_segment, track_name=track_name) + except Exception: + raise + + def save(self) -> None: + """保存草稿""" + try: + self.draft.save() + except Exception: + raise + + def add_subtitle( + self, + script: str, + track_name: str = "subtitle", + timbre: Optional[str] = "女声-晓晓", + rate: str = "+25%", + volume: str = "+0%", + ): + """ + 根据脚本生成文本和音频字幕 + :param track_name: 轨道名称 + :param script: 脚本 + :param timbre: 声音音色 + :param rate: 语速 + :param volume: 音量 + :return: 无 + """ + print("正在根据脚本生成文本和音频字幕...", end="") + # 添加文本轨道 + self.draft.add_track( + track_type=capcut.TrackType.text, + track_name=(text_track_name := f"{track_name}:text"), + ) + # 添加音频轨道 + self.draft.add_track( + track_type=capcut.TrackType.audio, + track_name=(audio_track_name := f"{track_name}:audio"), + ) + + start = 0 + for content in script.split(","): + # 根据文本内容合成语音并返回音频素材名称 + name, duration = self.synthesizer.generate_audio( + content, timbre, rate, volume + ) + # 添加文本片段 + self._add_text( + track_name=text_track_name, + content=content, + timerange=(start, duration), + style={"size": 12.0, "align": 1}, # 字号为12,对齐方式为水平居中 + clip_settings={"transform_y": -0.5}, # 垂直位移 + effect={"effect_id": "6896137858998930701"}, # 第二行第三列花字 + ) + # 添加音频片段 + self._add_audio( + track_name=audio_track_name, + name=name, + target_timerange=(start, duration), + ) + start += duration + + # 更新脚本持续时长 + self.duration = start + print("已完成") + + def add_video( + self, + track_name: str, + name: str, + target_timerange: Tuple[Optional[int, str], Optional[int, str]] = None, + source_timerange: Optional[ + Tuple[Optional[int, str], Optional[int, str]], + ] = None, + speed: Optional[float] = None, + volume: float = 1.0, + clip_settings: Optional[Dict[str, Any]] = None, + keyframes: Optional[List[Tuple[KeyframeProperty, str, float]]] = None, + animation: Optional[Dict[str, Any]] = None, + transition: Optional[Dict[str, Any]] = None, + background_filling: Optional[Tuple[str, Any]] = None, + ): + """ + 向指定轨道添加视频/图片片段 + :param track_name: 轨道名称 + :param name: 视频/图片素材名称 + :param target_timerange: 视频素材在轨道上的范围,包括开始时间和持续时长 + :param source_timerange: 截取视频素材范围,包括开始时间和持续时长 + :param speed: 播放速度 + :param volume: 播放音量 + :param clip_settings: 图像调节设置 + :param keyframes: 关键帧设置 + :param animation: 动画设置 + :param transition: 转场设置 + :param background_filling: 背景填充设置 + :return: 无 + """ + # 预设图像调节设置 + CLIPSETTINGS = { + "logo": { + "scale_x": 0.2, + "scale_y": 0.2, + "transform_x": -0.68, + "transform_y": 0.82, + } # 等比缩放至20%,移动至左上角 + } + # 根据轨道名称获取预设文本调节设置 + if track_name in CLIPSETTINGS and not clip_settings: + clip_settings = CLIPSETTINGS.get(track_name) + + print(f"正在向轨道 {track_name} 添加视频/图片片段...", end="") + track_name = f"{track_name}:video" + + # 添加视频轨道 + self.draft.add_track(track_type=capcut.TrackType.video, track_name=track_name) + + # 添加视频片段 + self._add_video( + track_name=track_name, + name=name, + target_timerange=( + target_timerange if target_timerange else (0, self.duration) + ), + source_timerange=source_timerange, + speed=speed, + volume=volume, + clip_settings=clip_settings, + keyframes=keyframes, + animation=animation, + transition=transition, + background_filling=background_filling, + ) + print("已完成") + + def add_text( + self, + track_name: str, + content: str, + timerange: Tuple[Optional[int, str], Optional[int, str]] = None, + border: Optional[Dict[str, Any]] = None, + background: Optional[Dict[str, Any]] = None, + font: Optional[str] = None, + style: Optional[Dict[str, Any]] = None, + clip_settings: Optional[Dict[str, Any]] = None, + bubble: Optional[Dict[str, Any]] = None, + effect: Optional[str] = None, + animation: Optional[Dict[str, Any]] = None, + ): + """ + 向指定轨道添加文本片段 + :param track_name: 轨道名称 + :param content: 文本内容 + :param timerange: 文本素材在轨道上的范围,包括开始时间和持续时长 + :param border: 文本描边设置 + :param background: 文本背景设置 + :param font: 字体类型 + :param style: 字体样式 + :param clip_settings: 文本调节设置 + :param bubble: 气泡设置 + :param effect: 花字设置 + :param animation: 动画设置 + :return: 无 + """ + # 预设文本描边设置 + BORDER = { + "disclaimer": { + "width": 60.0, + } # 描边宽度为60 + } + # 根据轨道名称获取预设文本描边设置 + if track_name in BORDER and not border: + border = BORDER.get(track_name) + + # 预设字体样式 + STYLE = { + "disclaimer": { + "size": 8.0, + "align": 1, + } # 字号为8,对齐方式为水平居中 + } + # 根据轨道名称获取预设字体样式 + if track_name in STYLE and not style: + style = STYLE.get(track_name) + + # 预设文本调节设置 + CLIPSETTINGS = { + "disclaimer": { + "transform_y": -0.8, + } # 垂直位移 + } + # 根据轨道名称获取预设字体样式 + if track_name in CLIPSETTINGS and not clip_settings: + clip_settings = CLIPSETTINGS.get(track_name) + + print(f"正在向轨道 {track_name} 添加文本片段...", end="") + track_name = f"{track_name}:text" + + # 添加文本轨道 + self.draft.add_track(track_type=capcut.TrackType.text, track_name=track_name) + + # 添加文本片段 + self._add_text( + track_name=track_name, + content=content, + timerange=(timerange if timerange else (0, self.duration)), + border=border, + background=background, + font=font, + style=style, + clip_settings=clip_settings, + bubble=bubble, + effect=effect, + animation=animation, + ) + print("已完成") + + def add_sticker( + self, + track_name: str, + resource_id: str, + target_timerange: Tuple[Optional[int, str], Optional[int, str]] = None, + clip_settings: Optional[Dict[str, Any]] = None, + ): + """ + 向指定轨道添加贴纸 + :param track_name: 轨道名称 + :param resource_id: 贴纸 resource_id,可先将贴纸保存为我的预设 + :param target_timerange: 贴纸在轨道上的范围,包括开始时间和持续时长 + :param clip_settings: 文本调节设置 + :return: 无 + """ + # 预设文本描边设置 + RESOURCEID = { + "7026858083393588487": { + "scale_x": 0.2, + "scale_y": 0.2, + "transform_x": -0.75, + "transform_y": -0.78, + } # 等比缩放至20%,移动至左上角 + } + # 根据轨道名称获取预设文本描边设置 + if resource_id in RESOURCEID and not clip_settings: + clip_settings = RESOURCEID.get(resource_id) + + print(f"正在向轨道 {track_name} 添加贴纸...", end="") + track_name = f"{track_name}:video" + + # 添加贴纸轨道 + self.draft.add_track(track_type=capcut.TrackType.sticker, track_name=track_name) + + # 添加贴纸 + self._add_sticker( + track_name=track_name, + resource_id=resource_id, + target_timerange=( + target_timerange if target_timerange else (0, self.duration) + ), + clip_settings=clip_settings, + ) + print("已完成") diff --git a/剪映脚本生成自动化/edgetts.py b/剪映脚本生成自动化/edgetts.py new file mode 100644 index 0000000..c2b9d3f --- /dev/null +++ b/剪映脚本生成自动化/edgetts.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +import asyncio +import uuid +from pathlib import Path +from typing import Optional, Tuple + +import edge_tts +from mutagen.mp3 import MP3 + + +class EdgeTTS: + """ + edge在线语音合成 + """ + + # 中文音色 + TIMBRES = { + "女声-晓晓": "zh-CN-XiaoxiaoNeural", + "女声-晓辰": "zh-CN-XiaochenNeural", + "女声-晓倩": "zh-CN-XiaoqianNeural", + } + + def __init__( + self, + materials_path: Path, + ): + """ + 初始化语音合成器 + :param materials_path: 素材文件夹路径 + """ + # 素材文件夹路径 + self.materials_path = materials_path + + def generate_audio( + self, + content: str, + timbre: Optional[str] = "女声-晓晓", + rate: str = "+0%", + volume: str = "+0%", + ) -> Tuple[str, int]: + """ + 根据文本内容合成语音并返回音频素材名称 + :param content: 文本内容 + :param timbre: 音色名称,例如女声-晓晓 + :param rate: 语速 + :param volume: 音量 + :return 音频素材名称和持续时长 + """ + # noinspection PyBroadException + try: + # 异步处理:根据文本内容合成语音并保存为音频素材 + async def _async_generate_audio(): + # 实例化Communicate + communicator = edge_tts.Communicate( + text=content, + voice=self.TIMBRES[timbre], + rate=rate, + volume=volume, + ) + # 音频素材名称 + name = f"{uuid.uuid4().hex[-16:].upper()}.mp3" + # 音频素材路径 + audio_path = self.materials_path / name + await communicator.save(audio_path := audio_path.as_posix()) + # 音频持续时长(单位为微妙) + duration = int(round(MP3(audio_path).info.length * 1000000)) + return name, duration + + # 同步调用异步逻辑,对外暴露纯同步接口 + return asyncio.run(_async_generate_audio()) + except Exception as exception: + raise RuntimeError( + f"根据文本内容合成语音并保存为音频素材发声异常:{str(exception)}" + ) diff --git a/剪映脚本生成自动化/main.py b/剪映脚本生成自动化/main.py index 0b417b5..d9d9c4d 100644 --- a/剪映脚本生成自动化/main.py +++ b/剪映脚本生成自动化/main.py @@ -4,625 +4,16 @@ 剪映脚本生成自动化 """ -import asyncio -import uuid -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple - -import edge_tts -import pycapcut as capcut -from mutagen.mp3 import MP3 -from pycapcut import trange -from pycapcut.keyframe import KeyframeProperty -from pycapcut.segment import ClipSettings +from draft import GenerateDraft -class EdgeTTS: - """ - edge在线语音合成 - """ - - # 中文音色 - TIMBRES = { - "女声-晓晓": "zh-CN-XiaoxiaoNeural", - "女声-晓辰": "zh-CN-XiaochenNeural", - "女声-晓倩": "zh-CN-XiaoqianNeural", - } - - def __init__( - self, - materials_path: Path, - ): - """ - 初始化语音合成器 - :param materials_path: 素材文件夹路径 - """ - # 素材文件夹路径 - self.materials_path = materials_path - - def generate_audio( - self, - content: str, - timbre: Optional[str] = "女声-晓晓", - rate: str = "+0%", - volume: str = "+0%", - ) -> Tuple[str, int]: - """ - 根据文本内容合成语音并返回音频素材名称 - :param content: 文本内容 - :param timbre: 音色名称,例如女声-晓晓 - :param rate: 语速 - :param volume: 音量 - :return 音频素材名称和持续时长 - """ - # noinspection PyBroadException - try: - # 异步处理:根据文本内容合成语音并保存为音频素材 - async def _async_generate_audio(): - # 实例化Communicate - communicator = edge_tts.Communicate( - text=content, - voice=self.TIMBRES[timbre], - rate=rate, - volume=volume, - ) - # 音频素材名称 - name = f"{uuid.uuid4().hex[-16:].upper()}.mp3" - # 音频素材路径 - audio_path = self.materials_path / name - await communicator.save(audio_path := audio_path.as_posix()) - # 音频持续时长(单位为微妙) - duration = int(round(MP3(audio_path).info.length * 1000000)) - return name, duration - - # 同步调用异步逻辑,对外暴露纯同步接口 - return asyncio.run(_async_generate_audio()) - except Exception as exception: - raise RuntimeError( - f"根据文本内容合成语音并保存为音频素材发声异常:{str(exception)}" - ) - - -class GenerateDraft: - """剪映脚本生成器""" - - # noinspection PyShadowingNames - def __init__( - self, - name: str, - video_width: int = 1080, - video_height: int = 1920, - video_fps: int = 30, - drafts_path: str = r"C:\Users\admin\AppData\Local\JianyingPro\User Data\Projects\com.lveditor.draft", - materials_path: str = r"C:\Users\admin\PycharmProjects\Python\剪映脚本生成自动化\materials", - ): - """ - 初始化生成剪映脚本 - :param name: 草稿名称 - :param video_width: 视频宽度 - :param video_height: 视频高度 - :param video_fps: 视频帧率 - :param drafts_path: 剪映草稿文件夹路径 - :param materials_path: 素材文件夹路径 - """ - print("正在初始化剪映脚本生成器...", end="") - # noinspection PyBroadException - try: - # 草稿名称 - self.name = name - # 视频宽度和高度 - self.video_width, self.video_height = video_width, video_height - # 视频帧率 - self.video_fps = video_fps - # 剪映草稿文件夹路径 - self.drafts_path = drafts_path - # 素材文件夹路径 - self.materials_path = Path(materials_path) # 转为path对象 - # 检查素材文件夹是否存在,若不存在则抛出异常 - if not self.materials_path.exists(): - raise FileNotFoundError(f"素材文件夹不存在") - - # 初始化草稿文件夹管理器 - self.draft_folder = capcut.DraftFolder(self.drafts_path) - - # 新建草稿 - self.draft = self.draft_folder.create_draft( - draft_name=self.name, - width=self.video_width, - height=self.video_height, - fps=self.video_fps, - allow_replace=True, # 允许覆盖 - ) - - # 脚本持续时长 - self.duration = 0 - # 实例化EdgeTTS - self.synthesizer = EdgeTTS(self.materials_path) - print("已完成") - except Exception as exception: - print(f"发生异常:{str(exception)}") - raise - - def _get_material(self, name: str) -> str: - """ - 获取素材 - :param name: 素材名称 - :return 素材路径 - """ - # 素材路径 - material_path = self.materials_path / name - if not material_path.exists(): - raise FileNotFoundError(f"素材文件不存在") - return material_path.as_posix() - - def _add_audio( - self, - track_name: str, - name: str, - target_timerange: Tuple[Optional[int, str], Optional[int, str]], - source_timerange: Optional[Tuple[str, str]] = None, - speed: Optional[float] = None, - volume: float = 1.0, - fade: Optional[Tuple[str, str]] = None, - ) -> None: - """ - 添加音频片段 - :param track_name: 轨道名称 - :param name: 音频素材名称 - :param target_timerange: 音频素材在轨道上的范围,包括开始时间和持续时长 - :param source_timerange: 截取音频素材范围,包括开始时间和持续时长,默认根据音频素材开始时间根据播放速度截取与音频素材持续时长等长的部分 - :param speed: 播放速度 - :param volume: 播放音量 - :param fade: 淡入淡出设置 - :return: 无 - """ - try: - # 构建音频片段 - audio_segment = capcut.AudioSegment( - material=self._get_material(name), - target_timerange=trange(*target_timerange), - source_timerange=( - trange(*source_timerange) if source_timerange else None - ), - speed=speed, - volume=volume, - ) - # 添加淡入淡出效果 - if fade: - audio_segment.add_fade(*fade) - - # 向指定轨道添加音频片段 - self.draft.add_segment(segment=audio_segment, track_name=track_name) - return - except Exception: - raise - - def _add_video( - self, - track_name: str, - name: str, - target_timerange: Tuple[Optional[int, str], Optional[int, str]], - source_timerange: Optional[ - Tuple[Optional[int, str], Optional[int, str]], - ] = None, - speed: Optional[float] = None, - volume: float = 1.0, - clip_settings: Optional[Dict[str, Any]] = None, - keyframes: Optional[List[Tuple[KeyframeProperty, str, float]]] = None, - animation: Optional[Dict[str, Any]] = None, - transition: Optional[Dict[str, Any]] = None, - background_filling: Optional[Tuple[str, Any]] = None, - ) -> None: - """ - 添加视频/图片片段 - :param track_name: 轨道名称 - :param name: 视频/图片素材名称 - :param target_timerange: 视频素材在轨道上的范围,包括开始时间和持续时长 - :param source_timerange: 截取视频素材范围,包括开始时间和持续时长 - :param speed: 播放速度 - :param volume: 播放音量 - :param clip_settings: 图像调节设置 - :param keyframes: 关键帧设置 - :param animation: 动画设置 - :param transition: 转场设置 - :param background_filling: 背景填充设置 - :param track_name: 轨道名称 - :return: 无 - """ - try: - # 构建视频/图片片段 - video_segment = capcut.VideoSegment( - material=self._get_material(name), - target_timerange=trange(*target_timerange), - source_timerange=( - trange(*source_timerange) if source_timerange else None - ), - speed=speed, - volume=volume, - clip_settings=ClipSettings(**clip_settings) if clip_settings else None, - ) - # (视频素材)添加关键帧 - if keyframes: - # noinspection PyShadowingBuiltins - for property, time, value in keyframes: - video_segment.add_keyframe(property, time, value) - - # (视频素材)添加动画 - if animation: - video_segment.add_animation(**animation) - - # (视频素材)添加转场 - if transition: - video_segment.add_transition(**transition) - - # (图片素材)添加背景填充 - if background_filling: - video_segment.add_background_filling(*background_filling) - - # 向指定轨道添加视频/图片片段 - self.draft.add_segment(segment=video_segment, track_name=track_name) - return - except Exception: - raise - - def _add_text( - self, - track_name: str, - content: str, - timerange: Tuple[Optional[int, str], Optional[int, str]], - border: Optional[Dict[str, Any]] = None, - background: Optional[Dict[str, Any]] = None, - font: Optional[str] = None, - style: Optional[Dict[str, Any]] = None, - clip_settings: Optional[Dict[str, Any]] = None, - bubble: Optional[Dict[str, Any]] = None, - effect: Optional[Dict[str, Any]] = None, - animation: Optional[Dict[str, Any]] = None, - ) -> None: - """ - 添加文本片段 - :param track_name: 轨道名称 - :param content: 文本内容 - :param timerange: 文本素材在轨道上的范围,包括开始时间和持续时长 - :param border: 文本描边设置 - :param background: 文本背景设置 - :param font: 字体类型 - :param style: 字体样式 - :param clip_settings: 文本调节设置 - :param bubble: 气泡设置 - :param effect: 花字设置 - :param animation: 动画设置 - :return: 无 - """ - try: - # 构建文本片段 - text_segment = capcut.TextSegment( - text=content, - timerange=trange(*timerange), - border=capcut.TextBorder(**border) if border else None, - background=capcut.TextBackground(**background) if background else None, - font=capcut.FontType(font) if font else None, - style=capcut.TextStyle(**style) if style else None, - clip_settings=( - capcut.ClipSettings(**clip_settings) if clip_settings else None - ), - ) - # 添加气泡 - if bubble: - text_segment.add_bubble(**bubble) - - # 添加花字 - if effect: - text_segment.add_effect( - **effect - ) # 可先将花字保存预设,再在C:/Users/admin/AppData/Local/JianyingPro/User Data/Presets/Text_V2/预设文本?.textpreset获取花字resource_id - - # 添加动画 - if animation: - text_segment.add_animation(**animation) - - # 向指定轨道添加文本片段 - self.draft.add_segment(segment=text_segment, track_name=track_name) - return - except Exception: - raise - - def _add_sticker( - self, - track_name: str, - resource_id: str, - target_timerange: Tuple[Optional[int, str], Optional[int, str]], - clip_settings: Optional[Dict[str, Any]] = None, - ) -> None: - """ - 添加贴纸片段 - :param track_name: 轨道名称 - :param resource_id: 贴纸 resource_id - :param target_timerange: 贴纸在轨道上的范围,包括开始时间和持续时长 - :param clip_settings: 文本调节设置 - :return: 无 - """ - try: - # 构建贴纸 - sticker_segment = capcut.StickerSegment( - resource_id=resource_id, # 可先将贴纸保存为我的预设,再在C:\Users\admin\AppData\Local\JianyingPro\User Data\Presets\Combination\Presets\我的预设?\preset_draft获取 - target_timerange=trange(*target_timerange), - clip_settings=( - capcut.ClipSettings(**clip_settings) if clip_settings else None - ), - ) - - # 向指定轨道添加贴纸 - self.draft.add_segment(segment=sticker_segment, track_name=track_name) - except Exception: - raise - - def save(self) -> None: - """保存草稿""" - try: - self.draft.save() - except Exception: - raise - - def add_subtitle( - self, - script: str, - track_name: str = "subtitle", - timbre: Optional[str] = "女声-晓晓", - rate: str = "+25%", - volume: str = "+0%", - ): - """ - 根据脚本生成文本和音频字幕 - :param track_name: 轨道名称 - :param script: 脚本 - :param timbre: 声音音色 - :param rate: 语速 - :param volume: 音量 - :return: 无 - """ - print("正在根据脚本生成文本和音频字幕...", end="") - # 添加文本轨道 - self.draft.add_track( - track_type=capcut.TrackType.text, - track_name=(text_track_name := f"{track_name}:text"), - ) - # 添加音频轨道 - self.draft.add_track( - track_type=capcut.TrackType.audio, - track_name=(audio_track_name := f"{track_name}:audio"), - ) - - start = 0 - for content in script.split(","): - # 根据文本内容合成语音并返回音频素材名称 - name, duration = self.synthesizer.generate_audio( - content, timbre, rate, volume - ) - # 添加文本片段 - self._add_text( - track_name=text_track_name, - content=content, - timerange=(start, duration), - style={"size": 12.0, "align": 1}, # 字号为12,对齐方式为水平居中 - clip_settings={"transform_y": -0.5}, # 垂直位移 - effect={"effect_id": "6896137858998930701"}, # 第二行第三列花字 - ) - # 添加音频片段 - self._add_audio( - track_name=audio_track_name, - name=name, - target_timerange=(start, duration), - ) - start += duration - - # 更新脚本持续时长 - self.duration = start - print("已完成") - - def add_video( - self, - track_name: str, - name: str, - target_timerange: Tuple[Optional[int, str], Optional[int, str]] = None, - source_timerange: Optional[ - Tuple[Optional[int, str], Optional[int, str]], - ] = None, - speed: Optional[float] = None, - volume: float = 1.0, - clip_settings: Optional[Dict[str, Any]] = None, - keyframes: Optional[List[Tuple[KeyframeProperty, str, float]]] = None, - animation: Optional[Dict[str, Any]] = None, - transition: Optional[Dict[str, Any]] = None, - background_filling: Optional[Tuple[str, Any]] = None, - ): - """ - 向指定轨道添加视频/图片片段 - :param track_name: 轨道名称 - :param name: 视频/图片素材名称 - :param target_timerange: 视频素材在轨道上的范围,包括开始时间和持续时长 - :param source_timerange: 截取视频素材范围,包括开始时间和持续时长 - :param speed: 播放速度 - :param volume: 播放音量 - :param clip_settings: 图像调节设置 - :param keyframes: 关键帧设置 - :param animation: 动画设置 - :param transition: 转场设置 - :param background_filling: 背景填充设置 - :return: 无 - """ - # 预设图像调节设置 - CLIPSETTINGS = { - "logo": { - "scale_x": 0.2, - "scale_y": 0.2, - "transform_x": -0.68, - "transform_y": 0.82, - } # 等比缩放至20%,移动至左上角 - } - # 根据轨道名称获取预设文本调节设置 - if track_name in CLIPSETTINGS and not clip_settings: - clip_settings = CLIPSETTINGS.get(track_name) - - print(f"正在向轨道 {track_name} 添加视频/图片片段...", end="") - track_name = f"{track_name}:video" - - # 添加视频轨道 - self.draft.add_track(track_type=capcut.TrackType.video, track_name=track_name) - - # 添加视频片段 - self._add_video( - track_name=track_name, - name=name, - target_timerange=( - target_timerange if target_timerange else (0, self.duration) - ), - source_timerange=source_timerange, - speed=speed, - volume=volume, - clip_settings=clip_settings, - keyframes=keyframes, - animation=animation, - transition=transition, - background_filling=background_filling, - ) - print("已完成") - - def add_text( - self, - track_name: str, - content: str, - timerange: Tuple[Optional[int, str], Optional[int, str]] = None, - border: Optional[Dict[str, Any]] = None, - background: Optional[Dict[str, Any]] = None, - font: Optional[str] = None, - style: Optional[Dict[str, Any]] = None, - clip_settings: Optional[Dict[str, Any]] = None, - bubble: Optional[Dict[str, Any]] = None, - effect: Optional[str] = None, - animation: Optional[Dict[str, Any]] = None, - ): - """ - 向指定轨道添加文本片段 - :param track_name: 轨道名称 - :param content: 文本内容 - :param timerange: 文本素材在轨道上的范围,包括开始时间和持续时长 - :param border: 文本描边设置 - :param background: 文本背景设置 - :param font: 字体类型 - :param style: 字体样式 - :param clip_settings: 文本调节设置 - :param bubble: 气泡设置 - :param effect: 花字设置 - :param animation: 动画设置 - :return: 无 - """ - # 预设文本描边设置 - BORDER = { - "disclaimer": { - "width": 60.0, - } # 描边宽度为60 - } - # 根据轨道名称获取预设文本描边设置 - if track_name in BORDER and not border: - border = BORDER.get(track_name) - - # 预设字体样式 - STYLE = { - "disclaimer": { - "size": 8.0, - "align": 1, - } # 字号为8,对齐方式为水平居中 - } - # 根据轨道名称获取预设字体样式 - if track_name in STYLE and not style: - style = STYLE.get(track_name) - - # 预设文本调节设置 - CLIPSETTINGS = { - "disclaimer": { - "transform_y": -0.8, - } # 垂直位移 - } - # 根据轨道名称获取预设字体样式 - if track_name in CLIPSETTINGS and not clip_settings: - clip_settings = CLIPSETTINGS.get(track_name) - - print(f"正在向轨道 {track_name} 添加文本片段...", end="") - track_name = f"{track_name}:text" - - # 添加文本轨道 - self.draft.add_track(track_type=capcut.TrackType.text, track_name=track_name) - - # 添加文本片段 - self._add_text( - track_name=track_name, - content=content, - timerange=(timerange if timerange else (0, self.duration)), - border=border, - background=background, - font=font, - style=style, - clip_settings=clip_settings, - bubble=bubble, - effect=effect, - animation=animation, - ) - print("已完成") - - def add_sticker( - self, - track_name: str, - resource_id: str, - target_timerange: Tuple[Optional[int, str], Optional[int, str]] = None, - clip_settings: Optional[Dict[str, Any]] = None, - ): - """ - 向指定轨道添加贴纸 - :param track_name: 轨道名称 - :param resource_id: 贴纸 resource_id,可先将贴纸保存为我的预设 - :param target_timerange: 贴纸在轨道上的范围,包括开始时间和持续时长 - :param clip_settings: 文本调节设置 - :return: 无 - """ - # 预设文本描边设置 - RESOURCEID = { - "7026858083393588487": { - "scale_x": 0.2, - "scale_y": 0.2, - "transform_x": -0.75, - "transform_y": -0.78, - } # 等比缩放至20%,移动至左上角 - } - # 根据轨道名称获取预设文本描边设置 - if resource_id in RESOURCEID and not clip_settings: - clip_settings = RESOURCEID.get(resource_id) - - print(f"正在向轨道 {track_name} 添加贴纸...", end="") - track_name = f"{track_name}:video" - - # 添加贴纸轨道 - self.draft.add_track(track_type=capcut.TrackType.sticker, track_name=track_name) - - # 添加贴纸 - self._add_sticker( - track_name=track_name, - resource_id=resource_id, - target_timerange=( - target_timerange if target_timerange else (0, self.duration) - ), - clip_settings=clip_settings, - ) - print("已完成") - - -# ======================== 调用示例(使用抽象后的方法) ======================== +# 编导方案1 def direct(): """生成剪映草稿""" # 实例化 draft = GenerateDraft( name="demo2", - ) + ) # 需要 # 根据脚本生成文本和音频字幕 draft.add_subtitle( diff --git a/票据理赔自动化/masterdata.py b/票据理赔自动化/masterdata.py index 8921ea4..f565f0e 100644 --- a/票据理赔自动化/masterdata.py +++ b/票据理赔自动化/masterdata.py @@ -160,19 +160,19 @@ class MasterData(SQLiteClient): report_date: str, ) -> Optional[List[Dict[str, Any]]]: """ - 根据保险分公司名称、被保险人姓名、证件类型、证件号码和出险时间查询责任列表 + 根据保险分公司名称、被保险人姓名、证件类型、证件号码和出险时间查询个单和责任数据 :param insurer_company: 保险分公司名称 :param insured_person: 被保险人姓名 :param identity_type: 证件类型 :param identity_number: 证件号码 :param report_date: 报案时间 - :return: 责任列表 + :return: 个单和责任数据 """ # noinspection PyBroadException try: with self: # noinspection SqlResolve - result = self._query_all( + results = self._query_all( sql=""" SELECT group_policies.group_policy, group_policies.insurer_company, @@ -187,6 +187,7 @@ class MasterData(SQLiteClient): person_policies.commencement_date) AS commencement_date, MIN(group_policies.termination_date, person_policies.termination_date) AS termination_date, + person_policies.guid AS person_policy_guid, liabilities.liability, liabilities.accident, liabilities.personal_self_ratio, @@ -232,31 +233,64 @@ class MasterData(SQLiteClient): report_date, ), ) - if result: - return [ + if results: + # 就个人自费比例、个人自付比例和合理比例转为小数(decimal对象),保险起期、止期则转为日期时间(datetime对象) + results = [ { k: ( - datetime.strptime(v, "%Y-%m-%d") - if k in ["commencement_date", "termination_date"] + Decimal(v).quantize( + Decimal("0.00"), + rounding=ROUND_HALF_UP, + ) + if k + in [ + "remaining_amount", + "personal_self_ratio", + "non_medical_ratio", + "reasonable_ratio", + ] else ( - Decimal(v).quantize( - Decimal("0.00"), - rounding=ROUND_HALF_UP, - ) - if k - in [ - "remaining_amount", - "personal_self_ratio", - "non_medical_ratio", - "reasonable_ratio", - ] + datetime.strptime(v, "%Y-%m-%d") + if k in ["commencement_date", "termination_date"] else v ) - ) # 就保险起期、止期则转为日期时间(datetime对象),个人自费比例、个人自付比例和合理比例转为小数(decimal对象) + ) for k, v in e.items() } - for e in result - ] # 将保险起期和保险止期转为日期(datetime对象) + for e in results + ] + + person_policies = {} # 重构数据结构 + for result in results: + liabilities = { + "liability": result["liability"], + "accident": result["accident"], + "personal_self_ratio": result["personal_self_ratio"], + "non_medical_ratio": result["non_medical_ratio"], + "reasonable_ratio": result["reasonable_ratio"], + "adjust_policy_guid": result["adjust_policy_guid"], + } + person_policy_guid = result["person_policy_guid"] + if person_policy_guid in person_policies: + person_policies[person_policy_guid]["liabilities"].append( + liabilities + ) + else: + person_policies[person_policy_guid] = { + "group_policy": result["group_policy"], + "insurer_company": result["insurer_company"], + "person_policy": result["person_policy"], + "master_insured_person": result["master_insured_person"], + "insured_person": result["insured_person"], + "identity_type": result["identity_type"], + "identity_number": result["identity_number"], + "relationship": result["relationship"], + "commencement_date": result["commencement_date"], + "termination_date": result["termination_date"], + "remaining_amount": result["remaining_amount"], + "liabilities": [liabilities], + } + return [v for k, v in person_policies.items()] raise RuntimeError("查无数据") # TODO: 若根据保险分公司名称、被保险人姓名、证件类型、证件号码和出险时间查询被保险人发生异常则流转至主数据人工处理 except Exception as exception: