@motionharness/bridge-davinci 0.0.0-stage → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +13 -0
- package/OPENFX_LICENSE +24 -0
- package/README.md +114 -2
- package/THIRD_PARTY_NOTICES.md +196 -0
- package/bridge-manifest.json +107 -0
- package/dist/inspector_runtime.py +113 -0
- package/dist/instance_runtime.py +469 -0
- package/dist/intrinsic_runtime.py +125 -0
- package/dist/legacy_conversion.py +619 -0
- package/dist/resolve_rpc.py +1508 -0
- package/dist/runner.mjs +22203 -0
- package/dist/runtime_io.py +31 -0
- package/package.json +35 -4
- package/resources/HotionHarness.ofx.bundle/Contents/Info.plist +9 -0
- package/resources/HotionHarness.ofx.bundle/Contents/MacOS/HotionHarness.ofx +0 -0
- package/resources/HotionHarnessInstall +0 -0
- package/resources/HotionHarnessStatus.app/Contents/Info.plist +12 -0
- package/resources/HotionHarnessStatus.app/Contents/MacOS/HotionHarnessStatus +0 -0
- package/resources/legacy-forwarder.lua +55 -0
- package/resources/legacy-inspector-v1.lua +31 -0
- package/resources/menu-entry.py +150 -0
- package/resources/sidebar-help/index.html +35 -0
- package/resources/sidebar-help/resolve-sidebar.png +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""File evidence and durable journals shared by the Resolve operations."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def file_digest(path):
|
|
9
|
+
digest = hashlib.sha256()
|
|
10
|
+
with open(path, "rb") as stream:
|
|
11
|
+
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
|
12
|
+
digest.update(block)
|
|
13
|
+
return "sha256:" + digest.hexdigest()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def atomic_json(path, value, *, allow_nan=True):
|
|
17
|
+
pending = path + "." + str(uuid.uuid4()) + ".pending"
|
|
18
|
+
try:
|
|
19
|
+
with os.fdopen(os.open(pending, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600), "w", encoding="utf-8") as stream:
|
|
20
|
+
json.dump(value, stream, ensure_ascii=False, allow_nan=allow_nan)
|
|
21
|
+
stream.flush()
|
|
22
|
+
os.fsync(stream.fileno())
|
|
23
|
+
os.replace(pending, path)
|
|
24
|
+
descriptor = os.open(os.path.dirname(path), os.O_RDONLY)
|
|
25
|
+
try:
|
|
26
|
+
os.fsync(descriptor)
|
|
27
|
+
finally:
|
|
28
|
+
os.close(descriptor)
|
|
29
|
+
finally:
|
|
30
|
+
if os.path.exists(pending):
|
|
31
|
+
os.unlink(pending)
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionharness/bridge-davinci",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.22",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/VibeMG/vibe-motion-graphics.git",
|
|
10
|
+
"directory": "packages/bridge/davinci"
|
|
11
|
+
},
|
|
12
|
+
"description": "Optional DaVinci Resolve adapter for the Hotion Harness CLI",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./dist/runner.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/runner.mjs",
|
|
18
|
+
"dist/*.py",
|
|
19
|
+
"bridge-manifest.json",
|
|
20
|
+
"resources",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"NOTICE",
|
|
24
|
+
"OPENFX_LICENSE",
|
|
25
|
+
"THIRD_PARTY_NOTICES.md"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22.18"
|
|
29
|
+
},
|
|
30
|
+
"os": [
|
|
31
|
+
"darwin"
|
|
32
|
+
],
|
|
33
|
+
"cpu": [
|
|
34
|
+
"arm64",
|
|
35
|
+
"x64"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0"><dict>
|
|
4
|
+
<key>CFBundleIdentifier</key><string>com.hotionharness.bridge.davinci</string>
|
|
5
|
+
<key>CFBundleName</key><string>Hotion Harness</string>
|
|
6
|
+
<key>CFBundleExecutable</key><string>HotionHarness.ofx</string>
|
|
7
|
+
<key>CFBundlePackageType</key><string>BNDL</string>
|
|
8
|
+
<key>CFBundleVersion</key><string>1</string>
|
|
9
|
+
</dict></plist>
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0"><dict>
|
|
4
|
+
<key>CFBundleIdentifier</key><string>com.hotionharness.bridge.status</string>
|
|
5
|
+
<key>CFBundleName</key><string>Hotion Harness</string>
|
|
6
|
+
<key>CFBundleDisplayName</key><string>Hotion Harness</string>
|
|
7
|
+
<key>CFBundleExecutable</key><string>HotionHarnessStatus</string>
|
|
8
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
9
|
+
<key>CFBundleVersion</key><string>1</string>
|
|
10
|
+
<key>CFBundleShortVersionString</key><string>1.0</string>
|
|
11
|
+
<key>LSUIElement</key><true/>
|
|
12
|
+
</dict></plist>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
-- Hotion Harness legacy entry forwarder v1
|
|
2
|
+
-- Explicit button callbacks only; image evaluation never executes this file.
|
|
3
|
+
local helper, config, workspace = @@HELPER@@, @@CONFIG@@, @@WORKSPACE@@
|
|
4
|
+
local function quote(value)
|
|
5
|
+
return "'" .. tostring(value):gsub("'", "'\\''") .. "'"
|
|
6
|
+
end
|
|
7
|
+
local function json_string(value)
|
|
8
|
+
assert(type(value) == 'string', 'Missing legacy entry identity')
|
|
9
|
+
return '"' .. value:gsub('[%z\1-\31\\"]', function(c)
|
|
10
|
+
if c == '\\' then return '\\\\' end
|
|
11
|
+
if c == '"' then return '\\"' end
|
|
12
|
+
return string.format('\\u%04x', string.byte(c))
|
|
13
|
+
end) .. '"'
|
|
14
|
+
end
|
|
15
|
+
local function clicked_target()
|
|
16
|
+
local application = Resolve()
|
|
17
|
+
local manager = application and application:GetProjectManager()
|
|
18
|
+
local project = manager and manager:GetCurrentProject()
|
|
19
|
+
local timeline = project and project:GetCurrentTimeline()
|
|
20
|
+
assert(timeline and not project:IsRenderingInProgress())
|
|
21
|
+
local video = nil
|
|
22
|
+
for _, item in ipairs(timeline:GetSelectedClips() or {}) do
|
|
23
|
+
if string.lower(item:GetType() or '') == 'video' then assert(video == nil); video = item end
|
|
24
|
+
end
|
|
25
|
+
assert(video)
|
|
26
|
+
return { projectId = project:GetUniqueId(), timelineId = timeline:GetUniqueId(), itemId = video:GetUniqueId() }
|
|
27
|
+
end
|
|
28
|
+
return function(request)
|
|
29
|
+
local ok, clicked = pcall(clicked_target)
|
|
30
|
+
local again_ok, again = pcall(clicked_target)
|
|
31
|
+
ok = ok and again_ok and clicked.projectId == again.projectId
|
|
32
|
+
and clicked.timelineId == again.timelineId and clicked.itemId == again.itemId
|
|
33
|
+
and type(request) == 'table' and (request.action == 'edit' or request.action == 'more')
|
|
34
|
+
if ok then
|
|
35
|
+
for _, key in ipairs({'workspaceKey', 'sourceId', 'bindingId'}) do
|
|
36
|
+
if type(request[key]) ~= 'string' or request[key] == '' then ok = false end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
local action, payload = 'legacy', nil
|
|
40
|
+
if ok then
|
|
41
|
+
local fields = {'"workspace":' .. json_string(workspace)}
|
|
42
|
+
for _, key in ipairs({'action', 'workspaceKey', 'sourceId', 'bindingId'}) do
|
|
43
|
+
fields[#fields + 1] = json_string(key) .. ':' .. json_string(request[key])
|
|
44
|
+
end
|
|
45
|
+
fields[#fields + 1] = '"clicked":{"projectId":' .. json_string(clicked.projectId)
|
|
46
|
+
.. ',"timelineId":' .. json_string(clicked.timelineId) .. ',"itemId":' .. json_string(clicked.itemId) .. '}'
|
|
47
|
+
payload = '{' .. table.concat(fields, ',') .. '}'
|
|
48
|
+
else
|
|
49
|
+
action = 'error'
|
|
50
|
+
payload = '{"workspace":' .. json_string(workspace)
|
|
51
|
+
.. ',"code":"SELECTION_UNAVAILABLE","message":"无法确认当前所选片段,请只选择一个 Hotion Harness 片段后重试。"}'
|
|
52
|
+
end
|
|
53
|
+
os.execute(quote(helper) .. ' --config ' .. quote(config) .. ' --action ' .. quote(action)
|
|
54
|
+
.. ' --request ' .. quote(payload) .. ' >/dev/null 2>&1 &')
|
|
55
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- MH Resolve Bridge inspector v1
|
|
2
|
+
-- Called only by an explicit Inspector button. Nothing executes during rendering.
|
|
3
|
+
local node, cli, workspace = @@NODE@@, @@CLI@@, @@WORKSPACE@@
|
|
4
|
+
local mh, node_dir = @@MH@@, @@NODE_DIR@@
|
|
5
|
+
|
|
6
|
+
local function quote(value)
|
|
7
|
+
return "'" .. tostring(value):gsub("'", "'\\''") .. "'"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
local function json_string(value)
|
|
11
|
+
assert(type(value) == 'string', 'Hotion Harness Bridge: missing Inspector identity')
|
|
12
|
+
return '"' .. value:gsub('[%z\1-\31\\"]', function(c)
|
|
13
|
+
if c == '\\' then return '\\\\' end
|
|
14
|
+
if c == '"' then return '\\"' end
|
|
15
|
+
return string.format('\\u%04x', string.byte(c))
|
|
16
|
+
end) .. '"'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
return function(request)
|
|
20
|
+
assert(type(request) == 'table', '请从 Hotion Harness Bridge 检查器按钮打开。')
|
|
21
|
+
assert(request.action == 'edit' or request.action == 'more', 'Hotion Harness Bridge: unknown action')
|
|
22
|
+
local fields = {}
|
|
23
|
+
for _, key in ipairs({'action', 'workspaceKey', 'sourceId', 'bindingId'}) do
|
|
24
|
+
fields[#fields + 1] = json_string(key) .. ':' .. json_string(request[key])
|
|
25
|
+
end
|
|
26
|
+
local payload = '{' .. table.concat(fields, ',') .. '}'
|
|
27
|
+
local command = 'PATH=' .. quote(node_dir) .. ':"$PATH" MH_BRIDGE_MH=' .. quote(mh) .. ' ' .. quote(node) .. ' ' .. quote(cli) .. ' inspector-action --workspace ' .. quote(workspace) .. ' --json ' .. quote(payload)
|
|
28
|
+
-- The controller opens public MH or the Bridge window. This UI callback
|
|
29
|
+
-- never waits on Resolve or MH, and never renders/synchronizes a video.
|
|
30
|
+
os.execute(command .. ' >/dev/null 2>&1 &')
|
|
31
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Installed menu: snapshot the selected video, then use the public CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import hashlib
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import uuid
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SelectionError(Exception):
|
|
13
|
+
def __init__(self, code, message):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.code = code
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def command_environment(config):
|
|
19
|
+
environment = dict(os.environ)
|
|
20
|
+
tools = config.get("tools") if isinstance(config.get("tools"), dict) else {}
|
|
21
|
+
paths = {name: path for name, path in tools.items()
|
|
22
|
+
if name in {"node", "pnpm", "ffmpeg", "ffprobe"}
|
|
23
|
+
and isinstance(path, str) and os.path.isabs(path)}
|
|
24
|
+
allowed = list(dict.fromkeys(os.path.dirname(path) for path in paths.values()))
|
|
25
|
+
preferred = config.get("toolDirectories")
|
|
26
|
+
preferred = preferred if isinstance(preferred, list) else []
|
|
27
|
+
directories = list(dict.fromkeys([value for value in preferred if value in allowed] + allowed))
|
|
28
|
+
if directories:
|
|
29
|
+
environment["PATH"] = os.pathsep.join(directories + [environment.get("PATH", "/usr/bin:/bin")])
|
|
30
|
+
for name in ("ffmpeg", "ffprobe"):
|
|
31
|
+
if name in paths:
|
|
32
|
+
environment["MH_" + name.upper()] = paths[name]
|
|
33
|
+
bridge_directory = config.get("bridgeDirectory")
|
|
34
|
+
if isinstance(bridge_directory, str) and os.path.isabs(bridge_directory):
|
|
35
|
+
environment["MH_BRIDGE_DIR"] = bridge_directory
|
|
36
|
+
return environment
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def selected_target(application, management=False):
|
|
40
|
+
if not application:
|
|
41
|
+
raise SelectionError("RESOLVE_UNAVAILABLE", "达芬奇尚未连接。请打开工程后重试。")
|
|
42
|
+
manager = application.GetProjectManager()
|
|
43
|
+
project = manager.GetCurrentProject()
|
|
44
|
+
timeline = project.GetCurrentTimeline() if project else None
|
|
45
|
+
if not timeline:
|
|
46
|
+
raise SelectionError("NO_TIMELINE", "请打开时间线并选择一个 Hotion Harness 片段。")
|
|
47
|
+
if project.IsRenderingInProgress():
|
|
48
|
+
raise SelectionError("RESOLVE_BUSY", "达芬奇正在导出,请完成后重试。")
|
|
49
|
+
selected = timeline.GetSelectedClips() or []
|
|
50
|
+
identifiers = sorted(item.GetUniqueId() for item in selected)
|
|
51
|
+
videos = [item for item in selected if item.GetType().lower() == "video"]
|
|
52
|
+
if len(videos) != 1:
|
|
53
|
+
raise SelectionError("SELECTION_AMBIGUOUS", "请只选择一个 Hotion Harness 视频片段。")
|
|
54
|
+
media = videos[0].GetMediaPoolItem()
|
|
55
|
+
if not media:
|
|
56
|
+
raise SelectionError("NOT_PLAIN_MEDIA", "所选片段没有普通视频素材。请先转换旧版生成器。")
|
|
57
|
+
encoded = (media.GetThirdPartyMetadata() or {}).get("motionharness.bridge") if media else None
|
|
58
|
+
try:
|
|
59
|
+
link = json.loads(encoded) if isinstance(encoded, str) else None
|
|
60
|
+
except (ValueError, TypeError):
|
|
61
|
+
link = None
|
|
62
|
+
linked = isinstance(link, dict) and link.get("schemaVersion") == 1 and all(
|
|
63
|
+
isinstance(link.get(key), str) and link[key] for key in ("bindingId", "sourceId", "artifactId")
|
|
64
|
+
)
|
|
65
|
+
if not linked and not management:
|
|
66
|
+
raise SelectionError("NOT_LINKED", "所选视频未关联 Hotion Harness 工程。")
|
|
67
|
+
database = manager.GetCurrentDatabase() if management else None
|
|
68
|
+
if management and (not isinstance(database, dict) or not database):
|
|
69
|
+
raise SelectionError("SCOPE_UNAVAILABLE", "无法确认当前工程位置,请检查达芬奇连接。")
|
|
70
|
+
current = manager.GetCurrentProject()
|
|
71
|
+
active = current.GetCurrentTimeline() if current else None
|
|
72
|
+
if not current or current.GetUniqueId() != project.GetUniqueId() or not active or active.GetUniqueId() != timeline.GetUniqueId() or sorted(
|
|
73
|
+
item.GetUniqueId() for item in (active.GetSelectedClips() or [])
|
|
74
|
+
) != identifiers:
|
|
75
|
+
raise SelectionError("SELECTION_CHANGED", "所选片段已改变,请重新点击。")
|
|
76
|
+
if management:
|
|
77
|
+
if manager.GetCurrentDatabase() != database:
|
|
78
|
+
raise SelectionError("SELECTION_CHANGED", "当前工程位置已改变,请重新点击。")
|
|
79
|
+
database_id = "sha256:" + hashlib.sha256(json.dumps(database, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()).hexdigest()
|
|
80
|
+
return {"selection": {"scope": {"databaseId": database_id, "projectId": project.GetUniqueId()},
|
|
81
|
+
"timelineId": timeline.GetUniqueId(), "itemId": videos[0].GetUniqueId(), "mediaPoolItemId": media.GetUniqueId(),
|
|
82
|
+
**({key: link[key] for key in ("bindingId", "sourceId", "artifactId")} if linked else {})}}
|
|
83
|
+
return {"bindingId": link["bindingId"], "selection": {
|
|
84
|
+
"projectId": project.GetUniqueId(), "mediaPoolItemId": media.GetUniqueId(),
|
|
85
|
+
"sourceId": link["sourceId"], "artifactId": link["artifactId"]}}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def resolve_application():
|
|
89
|
+
application = globals().get("resolve")
|
|
90
|
+
if application:
|
|
91
|
+
return application
|
|
92
|
+
try:
|
|
93
|
+
import DaVinciResolveScript
|
|
94
|
+
except ImportError:
|
|
95
|
+
# Installed SDK locations, as documented by the Resolve scripting API.
|
|
96
|
+
sdk = "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting/Modules"
|
|
97
|
+
if sys.platform == "darwin" and sdk not in sys.path:
|
|
98
|
+
sys.path.append(sdk)
|
|
99
|
+
import DaVinciResolveScript
|
|
100
|
+
return DaVinciResolveScript.scriptapp("Resolve")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main():
|
|
104
|
+
config_path = Path(__HOTION_CONFIG_PATH_JSON__)
|
|
105
|
+
status_path = Path(__HOTION_STATUS_PATH_JSON__)
|
|
106
|
+
action = __HOTION_ACTION_JSON__
|
|
107
|
+
if not status_path.is_absolute() or not status_path.is_file() or not os.access(status_path, os.X_OK):
|
|
108
|
+
raise RuntimeError("缺少 Hotion Harness 状态窗口,请运行 mh bridge davinci install 修复组件。")
|
|
109
|
+
repair = None
|
|
110
|
+
try:
|
|
111
|
+
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
112
|
+
if not isinstance(config, dict):
|
|
113
|
+
raise ValueError("invalid config")
|
|
114
|
+
except (OSError, ValueError, TypeError):
|
|
115
|
+
config = {}
|
|
116
|
+
repair = "桥接安装配置缺失或损坏,请运行 mh bridge davinci install 修复组件。"
|
|
117
|
+
if repair is None:
|
|
118
|
+
cli_path = config.get("cliPath")
|
|
119
|
+
executable = Path(cli_path) if isinstance(cli_path, str) and cli_path else None
|
|
120
|
+
if not executable or not executable.is_absolute() or not executable.is_file() or not os.access(executable, os.X_OK):
|
|
121
|
+
repair = "找不到 Hotion Harness 主程序,请运行 mh bridge davinci install 修复组件。"
|
|
122
|
+
request = {}
|
|
123
|
+
if isinstance(config.get("workspace"), str):
|
|
124
|
+
request["workspace"] = config["workspace"]
|
|
125
|
+
if repair:
|
|
126
|
+
action = "error"
|
|
127
|
+
request.update(code="INSTALL_MISSING", message=repair)
|
|
128
|
+
else:
|
|
129
|
+
try:
|
|
130
|
+
request.update(selected_target(resolve_application(), management=action == "manage"))
|
|
131
|
+
except SelectionError as error:
|
|
132
|
+
action = "error"
|
|
133
|
+
request.update(code=error.code, message=str(error))
|
|
134
|
+
except (ImportError, AttributeError, RuntimeError):
|
|
135
|
+
action = "error"
|
|
136
|
+
request.update(code="SELECTION_UNAVAILABLE", message="无法读取所选片段,请检查达芬奇脚本连接。")
|
|
137
|
+
if action == "update":
|
|
138
|
+
request["idempotencyKey"] = str(uuid.uuid4())
|
|
139
|
+
arguments = [str(status_path), "--config", str(config_path), "--action", action,
|
|
140
|
+
"--request", json.dumps(request, ensure_ascii=False)]
|
|
141
|
+
subprocess.Popen(arguments, stdin=subprocess.DEVNULL,
|
|
142
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
143
|
+
env=command_environment(config), close_fds=True, start_new_session=True)
|
|
144
|
+
print(request["message"] if action == "error" else "正在打开 Hotion Harness 工程…" if action == "edit" else
|
|
145
|
+
"正在读取所选 Hotion Harness 片段…" if action == "manage" else
|
|
146
|
+
"正在提交 Hotion Harness 请求,状态窗口将显示进度。")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<meta charset="utf-8">
|
|
4
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
|
+
<title>添加 Hotion Harness 侧栏入口</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root{color-scheme:light dark;font:15px/1.7 system-ui,-apple-system,sans-serif;color:#e9e9ed;background:#191a1e}
|
|
8
|
+
*{box-sizing:border-box}body{max-width:1080px;margin:auto;padding:32px 24px}h1{font-size:26px;line-height:1.3;margin:0 0 10px}p{margin:10px 0;color:#bfc0ca}
|
|
9
|
+
ol{padding-left:24px}li{padding-left:6px;margin:14px 0}strong{color:#fff}kbd,code{font:inherit;background:#303139;border-radius:5px;padding:2px 6px}
|
|
10
|
+
figure{position:relative;margin:24px 0 8px;border:1px solid #555661;border-radius:10px;overflow:hidden}img{display:block;width:100%;height:auto}
|
|
11
|
+
.pin{position:absolute;color:#fff;background:#275bd9;border:2px solid white;border-radius:50%;width:25px;height:25px;line-height:21px;text-align:center;font-size:13px;font-weight:700;box-shadow:0 1px 6px #000}
|
|
12
|
+
.one{left:13%;top:1%}.two{left:9%;top:7%}.three{left:23%;top:71%}
|
|
13
|
+
.caption{font-size:13px}details{margin:14px 0}summary{cursor:pointer;color:#9cbeff}.full{overflow:auto;max-height:70vh;border:1px solid #555661;margin-top:10px}.full img{width:1512px;max-width:none}
|
|
14
|
+
aside{border-top:1px solid #40414b;margin-top:24px;padding-top:16px}a{color:#9cbeff}
|
|
15
|
+
@media(max-width:520px){body{padding:20px 16px}h1{font-size:22px}.pin{width:20px;height:20px;line-height:16px;font-size:11px}}
|
|
16
|
+
</style>
|
|
17
|
+
<h1>添加侧栏编辑入口</h1>
|
|
18
|
+
<p>这是一个可选入口。通过桥接发送的素材已经关联源工程,添加效果不会重新导出视频。</p>
|
|
19
|
+
<ol>
|
|
20
|
+
<li>进入达芬奇的<strong>「剪辑」页</strong>,打开左上角的<strong>「特效库」</strong>(魔棒图标)。</li>
|
|
21
|
+
<li>点击特效库的放大镜,搜索 <strong>Hotion Harness</strong>。找到 Open FX 中的同名效果,将它<strong>拖到时间线上的目标动效片段</strong>。</li>
|
|
22
|
+
<li>选中这个片段,打开右侧<strong>「检查器 → 特效 → Open FX」</strong>,点击<strong>「编辑 Hotion Harness 工程」</strong>。</li>
|
|
23
|
+
</ol>
|
|
24
|
+
<figure>
|
|
25
|
+
<img src="resolve-sidebar.png" alt="达芬奇真实界面:左上方打开特效库,搜索 Hotion Harness,搜索结果可拖到下方时间线的动效示例片段。">
|
|
26
|
+
<span class="pin one" aria-hidden="true">1</span><span class="pin two" aria-hidden="true">2</span><span class="pin three" aria-hidden="true">3</span>
|
|
27
|
+
</figure>
|
|
28
|
+
<p class="caption">真实界面截图 · ① 特效库 ② 搜索框与效果列表 ③ 拖入目标片段。窗口宽度不同时,工具栏可能只显示图标。</p>
|
|
29
|
+
<details><summary>放大截图</summary><div class="full"><img src="resolve-sidebar.png" alt="可横向滚动查看的达芬奇界面大图"></div></details>
|
|
30
|
+
<aside>
|
|
31
|
+
<p><strong>快捷方式:</strong>先选中片段,按 <kbd>Shift</kbd> + <kbd>Space</kbd>,搜索 Hotion Harness 并点击添加。快捷键被自定义时,请使用上面的特效库路径。</p>
|
|
32
|
+
<p><strong>找不到效果?</strong>先安装桥接组件:<code>mh bridge davinci install</code>。安装完成后保存工程并重启达芬奇,再搜索。</p>
|
|
33
|
+
<p><strong>暂时不添加也可以:</strong>从「工作区 → 脚本 → Utility → Hotion Harness → 编辑工程」打开工程。已经添加过效果的片段无需重复添加。</p>
|
|
34
|
+
</aside>
|
|
35
|
+
</html>
|
|
Binary file
|