剪映APP的外挂:自定义素材库
最近工作比较清闲,闲下来的时间刚好思考能做些什么。由于现在视频剪辑大部分的使用工具还是剪映,便想着从剪映入手,看能不能做一些辅助的工具出来从而提升视频交付的质量与速度。结合技术热点,我计划使用agent搭建一个自动化生产剪映工程的机器人。
这其中最重要的就是生成剪映工程的tools了,好在这部分我已实现。但是另一个接踵而来的问题是:在剪映的app中,如何结合我们自己的素材库使用呢?比如根据标签召回、画面匹配等逻辑。
现有流程的痛点
让我们梳理一下当前的工作流程:
- 剪映编辑需要替换画面(识别需求:~5秒)
- 切换到浏览器,在素材库内搜索合适的画面(搜索+浏览:~30-60秒)
- 下载到本地(下载时间:~10-30秒,取决于文件大小)
- 回到剪映,使用替换功能(操作时间:~10秒)
单次替换总耗时:约55-105秒,且需要频繁切换应用。 如果一个5分钟的视频需要替换20-30个素材,整个过程可能耗费20-50分钟,并且频繁的应用切换会打断剪辑思路。
本文中,会实现在剪映替换素材界面直接接入自定义素材库,选择完毕后进行替换操作。
简单效果演示
文章最后会提供代码仓库地址
实现思路
一开始的思路比较异想天开:能否截取剪映的文件选择弹窗、换成我们自己选择的文件弹窗呢?在处理完成后返回文件地址不就好了?
但实际上,这种实现方案在cursor看来,无异于造核弹。原因是:这是系统级的拦截,“试图用 Python 去 hook 一个你不控制的 macOS 应用的Objective-C 运行时。这不是'困难',这是'疯狂'。”这是cursor的回答。仔细想想也是,如果这个功能很容易实现,那商业软件不是到处出现外挂插件了?确实,这条路目前是走不通的,而且可能还涉及到法律问题。
后来换了个思路:我们检测剪映的弹窗,只要是出现替换素材的弹窗我们便弹出自己的素材库,选择完毕后再自动化操作剪映的素材弹窗。那这里就有几个问题要处理:
- 如何检测剪映的“替换素材”窗口?不能是其它窗口;
- 弹出自定义的素材库,进行素材选择逻辑;
- 选择素材后,再返回操作剪映的"素材替换"窗口,选择我们的素材;
替换素材窗口检测
检测是否剪映APP在运行
使用系统接口,获取是否有名为“剪映专业版”的应用在运行
def is_jianying_running(self) -> bool:
"""检查剪映是否在运行"""
workspace = NSWorkspace.sharedWorkspace()
return any(
app.localizedName() == self.target_app_name
for app in workspace.runningApplications()
)
获取剪映APP的所有窗口
使用系统接口,获取所有“剪映专业版”的所属窗口
def _get_jianying_windows(self) -> list:
"""获取所有剪映窗口"""
window_list = Quartz.CGWindowListCopyWindowInfo(
Quartz.kCGWindowListOptionOnScreenOnly |
Quartz.kCGWindowListExcludeDesktopElements,
Quartz.kCGNullWindowID
)
if not window_list:
return []
return [
w for w in window_list
if w.get('kCGWindowOwnerName', '') == self.target_app_name
]
检测“素材选择”弹窗
这里经过尝试,window包含的所有内容只有:
{
"kCGWindowAlpha": 1,
"kCGWindowBounds": {
"Height": 498,
"Width": 704,
"X": 368,
"Y": 96
},
"kCGWindowIsOnscreen": 1,
"kCGWindowLayer": 0,
"kCGWindowMemoryUsage": 2288,
"kCGWindowName": "打开",
"kCGWindowNumber": 1197,
"kCGWindowOwnerName": "剪映专业版",
"kCGWindowOwnerPID": 16959,
"kCGWindowSharingState": 1,
"kCGWindowStoreType": 1
}
这其中并不包含弹窗的标题、内容以及类型等,所以,想从这点信息拿到是否是“素材选择”弹窗是不够的。 我们现在的任务,是从这一堆剪映的窗口中,超找是否有“素材选择”的弹窗。

最后选定的筛选条件是:窗口名为“打开”+窗口中包含字符串“请选择媒体资源”
由于我们知道各个窗口的包围盒,所以这里使用ocr技术去截取屏幕,识别是否包含“请选择媒体资源”,ocr的代码请移步仓库查看,这里的一个优化点事:我们将截图区域尽可能放在我们关注的部分,减少ocr的识别时间:
def has_text(self, text: str = "请选择媒体资源") -> dict:
"""检查剪映窗口是否包含指定文本"""
for w in self._get_jianying_windows():
if w.get('kCGWindowName', '') != "打开":
continue
original_bounds = w.get('kCGWindowBounds', {})
# 创建新的 bounds 副本,只截取上部区域
bounds = {
'X': original_bounds.get('X', 0) + 350,
'Y': original_bounds.get('Y', 0) + 10,
'Width': original_bounds.get('Width', 0) - 550,
'Height': 20 # 只截取顶部20像素
}
if WindowOCR.find_text_in_window(
w.get('kCGWindowNumber', 0),
text,
bounds=bounds,
cleanup=True
):
return w
return None
最终的识别时间:大约在0.3s,这个时间还可以接受。
自定义弹窗:接自己的素材库
在检测到剪映“素材选择”弹窗之后,我们弹出自己定义的“素材库”,由于我这里只是个演示,就弹一个系统的文件选择框吧。
def system_file_picker(default_dir: str = "~/Movies") -> str:
"""系统文件选择器"""
default_dir = os.path.expanduser(default_dir)
script = f'''
tell application "Finder"
activate
end tell
tell current application
set theFile to choose file with prompt "选择素材文件 - 剪映助手" default location POSIX file "{default_dir}" of type {{"public.movie", "public.image", "public.audio"}}
return POSIX path of theFile
end tell
'''
try:
result = subprocess.run(
['osascript', '-e', script],
capture_output=True,
text=True,
timeout=300
)
if result.returncode == 0:
return result.stdout.strip()
return None
except:
return None
调用函数,弹窗文件选择框,返回最后选择的文件路径。
操作剪映弹窗
现在到了最核心的问题了,如何操作剪映弹窗,将文件路径传入到剪映当中呢? 我们使用快捷键的方式:
- 激活剪映窗口;
- command+shift+G打开“前往”对话框;
- 输入路径;
- 回车确认;
代码如下:
def inject_path(file_path: str) -> bool:
"""激活剪映并注入路径"""
try:
script = f'''
tell application "System Events"
tell process "剪映专业版"
set frontmost to true
delay 0.2
keystroke "g" using {{command down, shift down}}
delay 0.2
keystroke "{file_path}"
delay 0.5
keystroke return
delay 0.5
keystroke return
delay 0.5
keystroke return
end tell
end tell
'''
result = subprocess.run(
['osascript', '-e', script],
capture_output=True,
text=True,
timeout=15
)
if result.returncode != 0:
print(f" 错误: {result.stderr}")
return False
return True
except Exception as e:
print(f" 异常: {e}")
return False
这里的延时是必须的,系统ui反应是需要时间的。
结尾
其它的代码实现,主要就是定时监测逻辑了。可优化项包含:
- 可以根据当前的系统状态,优化扫描次数(比如剪映未启动时,10s扫一次);
- 出现弹窗后,可以隐藏原素材选择框,等自定义素材库处理完后再显示;
- 除了ocr,是否还有更好的检查方式?