mirror of
https://github.com/GuanYixuan/pyJianYingDraft.git
synced 2024-11-25 16:22:53 +08:00
Implement basic Jianying_controller to conduct automatic exporting
This commit is contained in:
parent
32f1cbff2f
commit
da1b6a78c8
@ -20,8 +20,10 @@
|
||||
- ☑️ [修改文本片段的文本内容](#替换文本片段的内容)
|
||||
|
||||
### 批量导出
|
||||
- ⬜ 控制剪映打开指定草稿
|
||||
- ⬜ 控制剪映导出草稿至指定位置
|
||||
- ☑️ 控制剪映打开指定草稿
|
||||
- ☑️ 导出草稿至指定位置
|
||||
- ⬜ 调节部分导出参数
|
||||
- 感谢`@litter jump`提供部分思路
|
||||
|
||||
### 视频与图片
|
||||
- ☑️ 添加本地视频/图片素材
|
||||
|
@ -17,6 +17,7 @@ from .track import Track_type
|
||||
from .template_mode import Shrink_mode, Extend_mode
|
||||
from .script_file import Script_file
|
||||
from .draft_folder import Draft_folder
|
||||
from .jianying_controller import Jianying_controller
|
||||
|
||||
from .time_util import SEC, tim, trange
|
||||
|
||||
@ -50,6 +51,7 @@ __all__ = [
|
||||
"Extend_mode",
|
||||
"Script_file",
|
||||
"Draft_folder",
|
||||
"Jianying_controller",
|
||||
"SEC",
|
||||
"tim",
|
||||
"trange"
|
||||
|
@ -14,3 +14,10 @@ class AmbiguousMaterial(ValueError):
|
||||
|
||||
class ExtensionFailed(ValueError):
|
||||
"""替换素材时延伸片段失败"""
|
||||
|
||||
class DraftNotFound(NameError):
|
||||
"""未找到草稿"""
|
||||
class AutomationError(Exception):
|
||||
"""自动化操作失败"""
|
||||
class ExportTimeout(Exception):
|
||||
"""导出超时"""
|
||||
|
162
pyJianYingDraft/jianying_controller.py
Normal file
162
pyJianYingDraft/jianying_controller.py
Normal file
@ -0,0 +1,162 @@
|
||||
"""剪映自动化控制,主要与自动导出有关"""
|
||||
|
||||
import time
|
||||
import shutil
|
||||
import uiautomation as uia
|
||||
|
||||
from typing import Optional, Literal
|
||||
|
||||
from . import exceptions
|
||||
from .exceptions import AutomationError
|
||||
|
||||
class Jianying_controller:
|
||||
"""剪映控制器"""
|
||||
|
||||
app: uia.WindowControl
|
||||
"""剪映窗口"""
|
||||
app_status: Literal["home", "edit", "pre_export"]
|
||||
|
||||
def __init__(self):
|
||||
"""初始化剪映控制器, 此时剪映应该处于目录页"""
|
||||
self.get_window()
|
||||
|
||||
def export_draft(self, draft_name: str, output_dir: Optional[str] = None, timeout: float = 1e9) -> None:
|
||||
"""导出指定的剪映草稿
|
||||
|
||||
**注意: 需要确认有导出草稿的权限(不使用VIP功能或已开通VIP), 否则可能陷入死循环**
|
||||
|
||||
Args:
|
||||
draft_name (`str`): 要导出的剪映草稿名称
|
||||
output_path (`str`, optional): 导出路径, 导出完成后会将文件剪切到此, 不指定则使用剪映默认路径.
|
||||
timeout (`float`, optional): 导出超时时间(秒), 默认无限制.
|
||||
|
||||
Raises:
|
||||
`DraftNotFound`: 未找到指定名称的剪映草稿
|
||||
`AutomationError`: 剪映操作失败
|
||||
"""
|
||||
self.switch_to_home()
|
||||
|
||||
# 点击对应草稿
|
||||
draft_name_text = self.app.TextControl(searchDepth=2,
|
||||
Compare=lambda ctrl, depth: self.__draft_name_cmp(draft_name, ctrl, depth))
|
||||
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()
|
||||
|
||||
# 点击导出按钮
|
||||
export_btn = self.app.TextControl(searchDepth=2, Compare=self.__edit_page_export_cmp)
|
||||
if not export_btn.Exists(0):
|
||||
raise AutomationError("未找到导出按钮")
|
||||
export_btn.Click(simulateMove=False)
|
||||
time.sleep(3)
|
||||
self.get_window()
|
||||
|
||||
# 获取原始导出路径
|
||||
export_path_sib = self.app.TextControl(searchDepth=2, Compare=self.__export_path_cmp)
|
||||
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)
|
||||
|
||||
# 点击导出
|
||||
export_btn = self.app.TextControl(searchDepth=2, Compare=self.__export_btn_cmp)
|
||||
if not export_btn.Exists(0):
|
||||
raise AutomationError("未找到导出按钮")
|
||||
export_btn.Click(simulateMove=False)
|
||||
time.sleep(2)
|
||||
|
||||
# 等待导出完成
|
||||
st = time.time()
|
||||
while True:
|
||||
self.get_window()
|
||||
if self.app_status != "pre_export": continue
|
||||
|
||||
export_window = self.app.WindowControl(searchDepth=1, Name="JianyingPro")
|
||||
if export_window.Exists(0):
|
||||
close_btn = export_window.ButtonControl(Name="关闭")
|
||||
if close_btn.Exists(1):
|
||||
close_btn.Click(simulateMove=False)
|
||||
break
|
||||
|
||||
if time.time() - st > timeout:
|
||||
raise AutomationError("导出超时, 时限为%d秒" % timeout)
|
||||
|
||||
time.sleep(1)
|
||||
time.sleep(2)
|
||||
|
||||
# 复制导出的文件到指定目录
|
||||
if output_dir is not None:
|
||||
shutil.move(export_path, output_dir)
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def __draft_name_cmp(draft_name: str, control: uia.TextControl, depth: int) -> bool:
|
||||
if depth != 2:
|
||||
return False
|
||||
full_desc: str = control.GetPropertyValue(30159)
|
||||
return "Title:".lower() in full_desc.lower() and draft_name in full_desc
|
||||
|
||||
@staticmethod
|
||||
def __edit_page_export_cmp(control: uia.TextControl, depth: int) -> bool:
|
||||
if depth != 2:
|
||||
return False
|
||||
full_desc: str = control.GetPropertyValue(30159).lower()
|
||||
return "title" in full_desc and "export" in full_desc
|
||||
|
||||
@staticmethod
|
||||
def __export_btn_cmp(control: uia.TextControl, depth: int) -> bool:
|
||||
if depth != 2:
|
||||
return False
|
||||
full_desc: str = control.GetPropertyValue(30159).lower()
|
||||
return "ExportOkBtn".lower() == full_desc
|
||||
|
||||
@staticmethod
|
||||
def __export_path_cmp(control: uia.TextControl, depth: int) -> bool:
|
||||
if depth != 2:
|
||||
return False
|
||||
full_desc: str = control.GetPropertyValue(30159).lower()
|
||||
return "ExportPath".lower() in full_desc
|
Binary file not shown.
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 207 KiB |
@ -1,2 +1,3 @@
|
||||
pymediainfo
|
||||
imageio
|
||||
uiautomation>=2
|
||||
|
Loading…
Reference in New Issue
Block a user