【剪映小助手源码精讲】15_剪映控制器与自动化

60 阅读7分钟

第15章:剪映控制器与自动化

15.1 概述

剪映控制器(JianyingController)是剪映小助手中的重要组件,主要负责与剪映专业版桌面应用程序进行自动化交互。通过 Windows UI 自动化技术,实现对剪映软件的自动控制,包括草稿导出、窗口状态检测等功能。

本章将详细介绍剪映控制器的设计与实现,包括窗口状态检测机制、草稿导出功能、导出参数配置以及 Windows 平台自动化控制等核心功能。

15.2 核心枚举类定义

15.2.1 导出分辨率枚举

class ExportResolution(Enum):
    """导出分辨率"""
    RES_8K = "8K"
    RES_4K = "4K"
    RES_2K = "2K"
    RES_1080P = "1080P"
    RES_720P = "720P"
    RES_480P = "480P"

15.2.2 导出帧率枚举

class ExportFramerate(Enum):
    """导出帧率"""
    FR_24 = "24fps"
    FR_25 = "25fps"
    FR_30 = "30fps"
    FR_50 = "50fps"
    FR_60 = "60fps"

15.3 控件查找器(ControlFinder)

控件查找器提供了多种方式来定位剪映界面中的控件元素,支持基于描述文本和类名的查找方式。

15.3.1 描述文本匹配器

@staticmethod
def desc_matcher(target_desc: str, depth: int = 2, exact: bool = False) -> Callable[[uia.Control, int], bool]:
    """根据full_description查找控件的匹配器"""
    target_desc = target_desc.lower()
    def matcher(control: uia.Control, _depth: int) -> bool:
        if _depth != depth:
            return False
        full_desc: str = control.GetPropertyValue(30159).lower()
        return (target_desc == full_desc) if exact else (target_desc in full_desc)
    return matcher

15.3.2 类名匹配器

@staticmethod
def class_name_matcher(class_name: str, depth: int = 1, exact: bool = False) -> Callable[[uia.Control, int], bool]:
    """根据ClassName查找控件的匹配器"""
    class_name = class_name.lower()
    def matcher(control: uia.Control, _depth: int) -> bool:
        if _depth != depth:
            return False
        curr_class_name: str = control.ClassName.lower()
        return (class_name == curr_class_name) if exact else (class_name in curr_class_name)
    return matcher

15.4 剪映控制器核心实现

15.4.1 控制器初始化

class JianyingController:
    """剪映控制器"""

    app: uia.WindowControl
    """剪映窗口"""
    app_status: Literal["home", "edit", "pre_export"]

    def __init__(self):
        """初始化剪映控制器, 此时剪映应该处于目录页"""
        self.get_window()

15.4.2 窗口状态检测机制

控制器通过窗口类名和名称来检测当前剪映的状态:

def __jianying_window_cmp(self, control: uia.WindowControl, depth: int) -> bool:
    if control.Name != "剪映专业版":
        return False
    if "HomePage".lower() in control.ClassName.lower():
        self.app_status = "home"
        return True
    if "MainWindow".lower() in control.ClassName.lower():
        self.app_status = "edit"
        return True
    return False

窗口状态包括:

  • home: 主页模式,显示草稿列表
  • edit: 编辑模式,显示视频编辑界面
  • pre_export: 导出模式,显示导出窗口

15.4.3 窗口获取与置顶

def get_window(self) -> None:
    """寻找剪映窗口并置顶"""
    if hasattr(self, "app") and self.app.Exists(0):
        self.app.SetTopmost(False)

    self.app = uia.WindowControl(searchDepth=1, Compare=self.__jianying_window_cmp)
    if not self.app.Exists(0):
        raise AutomationError("剪映窗口未找到")

    # 寻找可能存在的导出窗口
    export_window = self.app.WindowControl(searchDepth=1, Name="导出")
    if export_window.Exists(0):
        self.app = export_window
        self.app_status = "pre_export"

    self.app.SetActive()
    self.app.SetTopmost()

15.5 草稿导出功能实现

15.5.1 导出主函数

导出功能是剪映控制器的核心功能,支持自定义分辨率、帧率和输出路径:

def export_draft(self, draft_name: str, output_path: Optional[str] = None, *,
                 resolution: Optional[ExportResolution] = None,
                 framerate: Optional[ExportFramerate] = None,
                 timeout: float = 1200) -> None:
    """导出指定的剪映草稿, **目前仅支持剪映6及以下版本**

    **注意: 需要确认有导出草稿的权限(不使用VIP功能或已开通VIP), 否则可能陷入死循环**

    Args:
        draft_name (`str`): 要导出的剪映草稿名称
        output_path (`str`, optional): 导出路径, 支持指向文件夹或直接指向文件, 不指定则使用剪映默认路径.
        resolution (`Export_resolution`, optional): 导出分辨率, 默认不改变剪映导出窗口中的设置.
        framerate (`Export_framerate`, optional): 导出帧率, 默认不改变剪映导出窗口中的设置.
        timeout (`float`, optional): 导出超时时间(秒), 默认为20分钟.

    Raises:
        `DraftNotFound`: 未找到指定名称的剪映草稿
        `AutomationError`: 剪映操作失败
    """

15.5.2 导出流程详解

1. 草稿选择与打开
# 点击对应草稿
draft_name_text = self.app.TextControl(
    searchDepth=2,
    Compare=ControlFinder.desc_matcher(f"HomePageDraftTitle:{draft_name}", exact=True)
)
if not draft_name_text.Exists(0):
    raise exceptions.DraftNotFound(f"未找到名为{draft_name}的剪映草稿")
draft_btn = draft_name_text.GetParentControl()
assert draft_btn is not None
draft_btn.Click(simulateMove=False)
time.sleep(10)
self.get_window()
2. 进入导出界面
# 点击导出按钮
export_btn = self.app.TextControl(searchDepth=2, Compare=ControlFinder.desc_matcher("MainWindowTitleBarExportBtn"))
if not export_btn.Exists(0):
    raise AutomationError("未在编辑窗口中找到导出按钮")
export_btn.Click(simulateMove=False)
time.sleep(10)
self.get_window()
3. 获取原始导出路径
# 获取原始导出路径(带后缀名)
export_path_sib = self.app.TextControl(searchDepth=2, Compare=ControlFinder.desc_matcher("ExportPath"))
if not export_path_sib.Exists(0):
    raise AutomationError("未找到导出路径框")
export_path_text = export_path_sib.GetSiblingControl(lambda ctrl: True)
assert export_path_text is not None
export_path = export_path_text.GetPropertyValue(30159)

15.5.3 导出参数配置

分辨率设置
# 设置分辨率
if resolution is not None:
    setting_group = self.app.GroupControl(searchDepth=1,
                                          Compare=ControlFinder.class_name_matcher("PanelSettingsGroup_QMLTYPE"))
    if not setting_group.Exists(0):
        raise AutomationError("未找到导出设置组")
    resolution_btn = setting_group.TextControl(searchDepth=2, Compare=ControlFinder.desc_matcher("ExportSharpnessInput"))
    if not resolution_btn.Exists(0.5):
        raise AutomationError("未找到导出分辨率下拉框")
    resolution_btn.Click(simulateMove=False)
    time.sleep(0.5)
    resolution_item = self.app.TextControl(
        searchDepth=2, Compare=ControlFinder.desc_matcher(resolution.value)
    )
    if not resolution_item.Exists(0.5):
        raise AutomationError(f"未找到{resolution.value}分辨率选项")
    resolution_item.Click(simulateMove=False)
    time.sleep(0.5)
帧率设置
# 设置帧率
if framerate is not None:
    setting_group = self.app.GroupControl(searchDepth=1,
                                          Compare=ControlFinder.class_name_matcher("PanelSettingsGroup_QMLTYPE"))
    if not setting_group.Exists(0):
        raise AutomationError("未找到导出设置组")
    framerate_btn = setting_group.TextControl(searchDepth=2, Compare=ControlFinder.desc_matcher("FrameRateInput"))
    if not framerate_btn.Exists(0.5):
        raise AutomationError("未找到导出帧率下拉框")
    framerate_btn.Click(simulateMove=False)
    time.sleep(0.5)
    framerate_item = self.app.TextControl(
        searchDepth=2, Compare=ControlFinder.desc_matcher(framerate.value)
    )
    if not framerate_item.Exists(0.5):
        raise AutomationError(f"未找到{framerate.value}帧率选项")
    framerate_item.Click(simulateMove=False)
    time.sleep(0.5)

15.5.4 导出执行与状态监控

开始导出
# 点击导出
export_btn = self.app.TextControl(searchDepth=2, Compare=ControlFinder.desc_matcher("ExportOkBtn", exact=True))
if not export_btn.Exists(0):
    raise AutomationError("未在导出窗口中找到导出按钮")
export_btn.Click(simulateMove=False)
time.sleep(5)
等待导出完成
# 等待导出完成
st = time.time()
while True:
    self.get_window()
    if self.app_status != "pre_export": continue

    succeed_close_btn = self.app.TextControl(searchDepth=2, Compare=ControlFinder.desc_matcher("ExportSucceedCloseBtn"))
    if succeed_close_btn.Exists(0):
        succeed_close_btn.Click(simulateMove=False)
        break

    if time.time() - st > timeout:
        raise AutomationError("导出超时, 时限为%d秒" % timeout)

    time.sleep(1)
文件复制
# 复制导出的文件到指定目录
if output_path is not None:
    shutil.move(export_path, output_path)

print(f"导出 {draft_name}{output_path} 完成")

15.6 窗口切换功能

15.6.1 切换到主页

def switch_to_home(self) -> None:
    """切换到剪映主页"""
    if self.app_status == "home":
        return
    if self.app_status != "edit":
        raise AutomationError("仅支持从编辑模式切换到主页")
    close_btn = self.app.GroupControl(searchDepth=1, ClassName="TitleBarButton", foundIndex=3)
    close_btn.Click(simulateMove=False)
    time.sleep(2)
    self.get_window()

15.7 异常处理机制

控制器定义了专门的异常类型来处理自动化过程中的错误:

from .exceptions import AutomationError

class DraftNotFound(Exception):
    """未找到草稿异常"""
    pass

15.8 使用示例

15.8.1 基本导出示例

from src.pyJianYingDraft.jianying_controller import JianyingController, ExportResolution, ExportFramerate

# 创建控制器
controller = JianyingController()

# 导出草稿到默认路径
controller.export_draft("我的视频草稿")

# 导出草稿到指定路径,设置分辨率和帧率
controller.export_draft(
    "我的视频草稿",
    output_path="D:/Videos/my_video.mp4",
    resolution=ExportResolution.RES_1080P,
    framerate=ExportFramerate.FR_30,
    timeout=1800  # 30分钟超时
)

15.8.2 错误处理示例

try:
    controller = JianyingController()
    controller.export_draft("不存在的草稿")
except DraftNotFound as e:
    print(f"草稿未找到: {e}")
except AutomationError as e:
    print(f"自动化操作失败: {e}")
except Exception as e:
    print(f"其他错误: {e}")

15.9 技术特点与限制

15.9.1 技术特点

  1. UI 自动化: 基于 uiautomation 库实现 Windows 平台 UI 自动化
  2. 状态检测: 智能检测剪映窗口状态,支持多模式切换
  3. 参数配置: 支持导出分辨率、帧率等参数的动态配置
  4. 异常处理: 完善的异常处理机制,提供详细的错误信息
  5. 超时控制: 支持导出超时设置,防止无限等待

15.9.2 使用限制

  1. 版本限制: 目前仅支持剪映6及以下版本
  2. 平台限制: 仅支持 Windows 平台
  3. 权限要求: 需要确认有导出草稿的权限(不使用 VIP 功能或已开通 VIP)
  4. 稳定性: UI 自动化受界面变化影响,需要定期维护更新

15.10 总结

剪映控制器通过 Windows UI 自动化技术,实现了对剪映专业版桌面应用程序的自动控制。该组件主要服务于需要自动化导出视频的场景,提供了完整的草稿导出解决方案。

核心功能包括:

  • 智能窗口状态检测与切换
  • 支持自定义导出参数(分辨率、帧率)
  • 完善的异常处理和超时机制
  • 灵活的文件输出路径配置

虽然在版本和平台方面存在一定限制,但剪映控制器为视频编辑自动化提供了重要的技术支撑,是剪映小助手实现完整自动化流程的关键组件。


相关资源