mirror of
https://github.com/sun-guannan/CapCutAPI.git
synced 2025-11-25 11:29:32 +08:00
318 lines
12 KiB
Python
318 lines
12 KiB
Python
|
|
import uiautomation as auto
|
|||
|
|
import time
|
|||
|
|
import json
|
|||
|
|
from typing import Dict, List, Optional
|
|||
|
|
|
|||
|
|
class JianYingUIInspector:
|
|||
|
|
def __init__(self):
|
|||
|
|
self.jianying_window = None
|
|||
|
|
|
|||
|
|
def find_jianying_window(self) -> bool:
|
|||
|
|
"""查找剪映窗口"""
|
|||
|
|
# 尝试多种可能的窗口名称
|
|||
|
|
window_names = [
|
|||
|
|
"剪映",
|
|||
|
|
"JianYing",
|
|||
|
|
"CapCut",
|
|||
|
|
"剪映专业版"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for name in window_names:
|
|||
|
|
try:
|
|||
|
|
# 按窗口标题查找
|
|||
|
|
window = auto.WindowControl(searchDepth=1, Name=name)
|
|||
|
|
if window.Exists(0, 0):
|
|||
|
|
self.jianying_window = window
|
|||
|
|
print(f"找到剪映窗口: {name}")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 按类名查找(如果知道的话)
|
|||
|
|
window = auto.WindowControl(searchDepth=1, ClassName="Qt5QWindowIcon")
|
|||
|
|
if window.Exists(0, 0) and name.lower() in window.Name.lower():
|
|||
|
|
self.jianying_window = window
|
|||
|
|
print(f"找到剪映窗口: {window.Name}")
|
|||
|
|
return True
|
|||
|
|
except Exception as e:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 如果按名称找不到,尝试遍历所有窗口
|
|||
|
|
print("按名称未找到,正在遍历所有窗口...")
|
|||
|
|
for window in auto.GetRootControl().GetChildren():
|
|||
|
|
if window.ControlType == auto.ControlType.WindowControl:
|
|||
|
|
window_title = window.Name
|
|||
|
|
if any(keyword in window_title for keyword in ["剪映", "JianYing", "CapCut"]):
|
|||
|
|
self.jianying_window = window
|
|||
|
|
print(f"找到剪映窗口: {window_title}")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
print("未找到剪映窗口,请确保剪映正在运行")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def get_control_info(self, control) -> Dict:
|
|||
|
|
"""获取控件的详细信息"""
|
|||
|
|
try:
|
|||
|
|
info = {
|
|||
|
|
"Name": control.Name,
|
|||
|
|
"ControlType": control.ControlTypeName,
|
|||
|
|
"ClassName": getattr(control, 'ClassName', ''),
|
|||
|
|
"AutomationId": getattr(control, 'AutomationId', ''),
|
|||
|
|
"BoundingRectangle": {
|
|||
|
|
"left": control.BoundingRectangle.left,
|
|||
|
|
"top": control.BoundingRectangle.top,
|
|||
|
|
"right": control.BoundingRectangle.right,
|
|||
|
|
"bottom": control.BoundingRectangle.bottom,
|
|||
|
|
"width": control.BoundingRectangle.width(),
|
|||
|
|
"height": control.BoundingRectangle.height()
|
|||
|
|
},
|
|||
|
|
"IsEnabled": control.IsEnabled,
|
|||
|
|
"IsVisible": getattr(control, 'IsOffscreen', True) == False,
|
|||
|
|
"ProcessId": getattr(control, 'ProcessId', 0)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 获取FullDescription属性 (属性ID: 30159)
|
|||
|
|
try:
|
|||
|
|
if hasattr(control, 'GetCurrentPropertyValue'):
|
|||
|
|
full_description = control.GetCurrentPropertyValue(30159)
|
|||
|
|
info["FullDescription"] = full_description or ""
|
|||
|
|
else:
|
|||
|
|
info["FullDescription"] = ""
|
|||
|
|
except Exception as e:
|
|||
|
|
info["FullDescription"] = ""
|
|||
|
|
info["FullDescriptionError"] = str(e)
|
|||
|
|
|
|||
|
|
# 获取文本内容的多种方式
|
|||
|
|
text_content = ""
|
|||
|
|
|
|||
|
|
# 方式1: 通过Value属性获取
|
|||
|
|
try:
|
|||
|
|
if hasattr(control, 'GetValuePattern'):
|
|||
|
|
value_pattern = control.GetValuePattern()
|
|||
|
|
if value_pattern:
|
|||
|
|
text_content = value_pattern.Value
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 方式2: 通过Text属性获取
|
|||
|
|
try:
|
|||
|
|
if hasattr(control, 'GetTextPattern'):
|
|||
|
|
text_pattern = control.GetTextPattern()
|
|||
|
|
if text_pattern:
|
|||
|
|
text_content = text_pattern.DocumentRange.GetText(-1)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 方式3: 直接通过属性获取
|
|||
|
|
try:
|
|||
|
|
if not text_content and hasattr(control, 'CurrentValue'):
|
|||
|
|
text_content = str(control.CurrentValue)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 方式4: 通过LegacyIAccessible获取
|
|||
|
|
try:
|
|||
|
|
if not text_content and hasattr(control, 'GetLegacyIAccessiblePattern'):
|
|||
|
|
legacy_pattern = control.GetLegacyIAccessiblePattern()
|
|||
|
|
if legacy_pattern:
|
|||
|
|
text_content = legacy_pattern.CurrentValue or legacy_pattern.CurrentName
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 方式5: 对于QML控件,尝试获取特定属性
|
|||
|
|
try:
|
|||
|
|
if not text_content and "QQuickText" in info["ClassName"]:
|
|||
|
|
# QML Text控件可能需要特殊处理
|
|||
|
|
if hasattr(control, 'GetCurrentPropertyValue'):
|
|||
|
|
# 尝试获取Text属性
|
|||
|
|
text_content = control.GetCurrentPropertyValue(auto.PropertyId.ValueValueProperty)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
info["TextContent"] = text_content or ""
|
|||
|
|
info["HasText"] = bool(text_content)
|
|||
|
|
|
|||
|
|
# 原有的其他属性获取
|
|||
|
|
try:
|
|||
|
|
info["Value"] = control.GetValuePattern().Value if hasattr(control, 'GetValuePattern') else ""
|
|||
|
|
except:
|
|||
|
|
info["Value"] = ""
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
info["IsSelected"] = control.GetSelectionItemPattern().IsSelected if hasattr(control, 'GetSelectionItemPattern') else False
|
|||
|
|
except:
|
|||
|
|
info["IsSelected"] = False
|
|||
|
|
|
|||
|
|
return info
|
|||
|
|
except Exception as e:
|
|||
|
|
return {"Error": str(e), "Name": "获取信息失败"}
|
|||
|
|
|
|||
|
|
def build_ui_tree(self, control, max_depth: int = 10, current_depth: int = 0) -> Dict:
|
|||
|
|
"""递归构建UI元素树"""
|
|||
|
|
if current_depth >= max_depth:
|
|||
|
|
return {"MaxDepthReached": True}
|
|||
|
|
|
|||
|
|
node = self.get_control_info(control)
|
|||
|
|
node["Depth"] = current_depth
|
|||
|
|
node["Children"] = []
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
children = control.GetChildren()
|
|||
|
|
for child in children:
|
|||
|
|
if child.Exists(0, 0): # 检查子控件是否存在
|
|||
|
|
child_node = self.build_ui_tree(child, max_depth, current_depth + 1)
|
|||
|
|
node["Children"].append(child_node)
|
|||
|
|
except Exception as e:
|
|||
|
|
node["ChildrenError"] = str(e)
|
|||
|
|
|
|||
|
|
return node
|
|||
|
|
|
|||
|
|
def print_ui_tree(self, node: Dict, indent: str = ""):
|
|||
|
|
"""打印UI树结构"""
|
|||
|
|
control_type = node.get("ControlType", "Unknown")
|
|||
|
|
name = node.get("Name", "")
|
|||
|
|
class_name = node.get("ClassName", "")
|
|||
|
|
automation_id = node.get("AutomationId", "")
|
|||
|
|
full_description = node.get("FullDescription", "")
|
|||
|
|
|
|||
|
|
# 构建显示文本
|
|||
|
|
display_parts = [control_type]
|
|||
|
|
if name:
|
|||
|
|
display_parts.append(f'Name="{name}"')
|
|||
|
|
if class_name:
|
|||
|
|
display_parts.append(f'Class="{class_name}"')
|
|||
|
|
if automation_id:
|
|||
|
|
display_parts.append(f'Id="{automation_id}"')
|
|||
|
|
if full_description:
|
|||
|
|
# 限制FullDescription显示长度,避免输出过长
|
|||
|
|
desc_display = full_description[:100] + "..." if len(full_description) > 100 else full_description
|
|||
|
|
display_parts.append(f'FullDesc="{desc_display}"')
|
|||
|
|
|
|||
|
|
display_text = " ".join(display_parts)
|
|||
|
|
|
|||
|
|
# 添加位置信息
|
|||
|
|
if "BoundingRectangle" in node:
|
|||
|
|
rect = node["BoundingRectangle"]
|
|||
|
|
display_text += f" [{rect['left']},{rect['top']},{rect['width']}x{rect['height']}]"
|
|||
|
|
|
|||
|
|
print(f"{indent}{display_text}")
|
|||
|
|
|
|||
|
|
# 递归打印子节点
|
|||
|
|
for child in node.get("Children", []):
|
|||
|
|
self.print_ui_tree(child, indent + " ")
|
|||
|
|
|
|||
|
|
def save_ui_tree_to_file(self, tree: Dict, filename: str = "jianying_ui_tree.json"):
|
|||
|
|
"""保存UI树到文件"""
|
|||
|
|
try:
|
|||
|
|
with open(filename, 'w', encoding='utf-8') as f:
|
|||
|
|
json.dump(tree, f, ensure_ascii=False, indent=2)
|
|||
|
|
print(f"UI树已保存到: {filename}")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"保存文件失败: {e}")
|
|||
|
|
|
|||
|
|
def find_elements_by_type(self, tree: Dict, control_type: str) -> List[Dict]:
|
|||
|
|
"""按控件类型查找元素"""
|
|||
|
|
results = []
|
|||
|
|
|
|||
|
|
if tree.get("ControlType") == control_type:
|
|||
|
|
results.append(tree)
|
|||
|
|
|
|||
|
|
for child in tree.get("Children", []):
|
|||
|
|
results.extend(self.find_elements_by_type(child, control_type))
|
|||
|
|
|
|||
|
|
return results
|
|||
|
|
|
|||
|
|
def find_elements_by_name(self, tree: Dict, name: str) -> List[Dict]:
|
|||
|
|
"""按名称查找元素"""
|
|||
|
|
results = []
|
|||
|
|
|
|||
|
|
if name.lower() in tree.get("Name", "").lower():
|
|||
|
|
results.append(tree)
|
|||
|
|
|
|||
|
|
for child in tree.get("Children", []):
|
|||
|
|
results.extend(self.find_elements_by_name(child, name))
|
|||
|
|
|
|||
|
|
return results
|
|||
|
|
|
|||
|
|
def find_text_controls(self, tree: Dict) -> List[Dict]:
|
|||
|
|
"""查找所有包含文本的控件"""
|
|||
|
|
results = []
|
|||
|
|
|
|||
|
|
if tree.get("HasText") and tree.get("TextContent"):
|
|||
|
|
results.append({
|
|||
|
|
"ControlType": tree.get("ControlType"),
|
|||
|
|
"Name": tree.get("Name"),
|
|||
|
|
"ClassName": tree.get("ClassName"),
|
|||
|
|
"TextContent": tree.get("TextContent"),
|
|||
|
|
"BoundingRectangle": tree.get("BoundingRectangle")
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
for child in tree.get("Children", []):
|
|||
|
|
results.extend(self.find_text_controls(child))
|
|||
|
|
|
|||
|
|
return results
|
|||
|
|
|
|||
|
|
def inspect_jianying_ui(self, max_depth: int = 8, save_to_file: bool = True):
|
|||
|
|
"""检查剪映UI并显示元素树"""
|
|||
|
|
print("正在查找剪映窗口...")
|
|||
|
|
|
|||
|
|
if not self.find_jianying_window():
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
print(f"窗口信息: {self.jianying_window.Name}")
|
|||
|
|
print(f"窗口位置: {self.jianying_window.BoundingRectangle}")
|
|||
|
|
print("\n正在构建UI元素树...")
|
|||
|
|
|
|||
|
|
# 构建UI树
|
|||
|
|
ui_tree = self.build_ui_tree(self.jianying_window, max_depth)
|
|||
|
|
|
|||
|
|
print("\n=== 剪映UI元素树 ===")
|
|||
|
|
self.print_ui_tree(ui_tree)
|
|||
|
|
|
|||
|
|
if save_to_file:
|
|||
|
|
self.save_ui_tree_to_file(ui_tree)
|
|||
|
|
|
|||
|
|
return ui_tree
|
|||
|
|
|
|||
|
|
def list_all_windows(self):
|
|||
|
|
"""列出所有窗口,帮助调试"""
|
|||
|
|
print("当前所有窗口:")
|
|||
|
|
for window in auto.GetRootControl().GetChildren():
|
|||
|
|
if window.ControlType == auto.ControlType.WindowControl:
|
|||
|
|
try:
|
|||
|
|
print(f"- {window.Name} (PID: {getattr(window, 'ProcessId', 'Unknown')})")
|
|||
|
|
except:
|
|||
|
|
print(f"- 无法获取窗口信息")
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
"""主函数"""
|
|||
|
|
inspector = JianYingUIInspector()
|
|||
|
|
|
|||
|
|
# 如果找不到剪映窗口,先列出所有窗口
|
|||
|
|
if not inspector.find_jianying_window():
|
|||
|
|
print("\n列出所有窗口以供参考:")
|
|||
|
|
inspector.list_all_windows()
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 检查UI
|
|||
|
|
ui_tree = inspector.inspect_jianying_ui(max_depth=6, save_to_file=True)
|
|||
|
|
|
|||
|
|
if ui_tree:
|
|||
|
|
# 示例:查找所有按钮
|
|||
|
|
buttons = inspector.find_elements_by_type(ui_tree, "ButtonControl")
|
|||
|
|
print(f"\n找到 {len(buttons)} 个按钮控件")
|
|||
|
|
|
|||
|
|
# 示例:查找包含"导出"的元素
|
|||
|
|
export_elements = inspector.find_elements_by_name(ui_tree, "导出")
|
|||
|
|
if export_elements:
|
|||
|
|
print(f"\n找到 {len(export_elements)} 个包含'导出'的元素:")
|
|||
|
|
for elem in export_elements:
|
|||
|
|
print(f" - {elem.get('ControlType')}: {elem.get('Name')}")
|
|||
|
|
|
|||
|
|
# 新增:查找所有包含文本的控件
|
|||
|
|
text_controls = inspector.find_text_controls(ui_tree)
|
|||
|
|
if text_controls:
|
|||
|
|
print(f"\n找到 {len(text_controls)} 个包含文本的控件:")
|
|||
|
|
for ctrl in text_controls:
|
|||
|
|
print(f" - {ctrl['ControlType']}: '{ctrl['TextContent']}'")
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|