@motionharness/bridge-davinci 0.0.23 → 0.0.25
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/README.md +11 -9
- package/bridge-manifest.json +37 -32
- package/dist/inspector_runtime.py +4 -4
- package/dist/instance_runtime.py +1 -1
- package/dist/legacy_conversion.py +3 -5
- package/dist/resolve_rpc.py +45 -69
- package/dist/runner.mjs +158 -152
- package/package.json +2 -2
- package/resources/HotionHarness.ofx.bundle/Contents/Info.plist +1 -1
- package/resources/HotionHarness.ofx.bundle/Contents/MacOS/HotionHarness.ofx +0 -0
- package/resources/{HotionHarnessStatus.app → MotionHarnessBridge.app}/Contents/Info.plist +3 -3
- package/resources/{HotionHarnessStatus.app/Contents/MacOS/HotionHarnessStatus → MotionHarnessBridge.app/Contents/MacOS/MotionHarnessBridge} +0 -0
- package/resources/MotionHarnessBridge.app/Contents/Resources/en.lproj/InfoPlist.strings +2 -0
- package/resources/MotionHarnessBridge.app/Contents/Resources/zh-Hans.lproj/InfoPlist.strings +2 -0
- package/resources/{HotionHarnessInstall → MotionHarnessInstall} +0 -0
- package/resources/legacy-forwarder.lua +1 -1
- package/resources/menu-entry.py +7 -7
- package/resources/sidebar-help/en.html +6 -8
- package/resources/sidebar-help/guide.css +14 -0
- package/resources/sidebar-help/index.html +6 -8
- package/resources/HotionHarnessStatus.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -2
- package/resources/HotionHarnessStatus.app/Contents/Resources/zh-Hans.lproj/InfoPlist.strings +0 -2
package/dist/resolve_rpc.py
CHANGED
|
@@ -29,8 +29,6 @@ import instance_runtime as instances
|
|
|
29
29
|
import legacy_conversion as legacy
|
|
30
30
|
from runtime_io import atomic_json, file_digest
|
|
31
31
|
|
|
32
|
-
# Frame boundaries were read back on this exact build; do not extrapolate to unknown builds.
|
|
33
|
-
VALIDATED_BUILD = "21.1.0.14"
|
|
34
32
|
SUPPORTED_INSPECTOR_EFFECTS = {
|
|
35
33
|
"Transform", "Source Transform", "Immersive Transform", "Cropping", "Source Cropping",
|
|
36
34
|
"Source Looks", "Dynamic Zoom", "Composite", "Lens Correction", "Retime and Scaling",
|
|
@@ -39,10 +37,6 @@ SUPPORTED_INSPECTOR_EFFECTS = {
|
|
|
39
37
|
ORIGINAL_PLAYBACK = {"perfProxyMediaMode": "0", "perfOptimisedMediaOn": "0", "perfRenderCacheMode": "none"}
|
|
40
38
|
|
|
41
39
|
|
|
42
|
-
def validated_host(resolve):
|
|
43
|
-
return resolve.GetVersionString() == VALIDATED_BUILD and sys.platform == "darwin" and platform.machine() == "arm64"
|
|
44
|
-
|
|
45
|
-
|
|
46
40
|
class BridgeFailure(Exception):
|
|
47
41
|
def __init__(self, code, message, **details):
|
|
48
42
|
super().__init__(message)
|
|
@@ -88,8 +82,7 @@ def project_for(resolve, requested=None, render_proof=None):
|
|
|
88
82
|
# GetCurrentDatabase returns None throughout some renders on 21.1.0.14.
|
|
89
83
|
# Monitoring/cancelling an already-started job may use its previously
|
|
90
84
|
# verified project + complete queue fingerprint; starting still requires DB.
|
|
91
|
-
if
|
|
92
|
-
and validated_host(resolve) and project.IsRenderingInProgress()):
|
|
85
|
+
if render_proof and requested and not manager.GetCurrentDatabase() and project.IsRenderingInProgress():
|
|
93
86
|
require(project.GetUniqueId() == requested["projectId"], "VERSION_CONFLICT", "运行中的任务已不属于原工程。")
|
|
94
87
|
job = render_job(project, render_proof["jobId"])
|
|
95
88
|
require(render_proof.get("expectedJobFingerprint") and fingerprint(job) == render_proof["expectedJobFingerprint"],
|
|
@@ -179,13 +172,13 @@ def sample_end_time(start, timeline_frames, source_fps, timeline_fps, source_fra
|
|
|
179
172
|
return min(source_frames / source_rate, start / source_rate + (timeline_frames - 1) / timeline_rate + 1 / source_rate)
|
|
180
173
|
|
|
181
174
|
|
|
182
|
-
def item_snapshot(item, timeline_id, track,
|
|
175
|
+
def item_snapshot(item, timeline_id, track, media_index=None, timeline_fps=None):
|
|
183
176
|
media = item.GetMediaPoolItem()
|
|
184
|
-
intrinsic_entry = intrinsic.read_entry(item, media_index or {}) if
|
|
177
|
+
intrinsic_entry = intrinsic.read_entry(item, media_index or {}) if not media else None
|
|
185
178
|
if intrinsic_entry:
|
|
186
179
|
media = media_index[intrinsic_entry['identity']['mediaPoolItemId']]
|
|
187
|
-
unsupported = []
|
|
188
|
-
kind = item.GetType()
|
|
180
|
+
unsupported = []
|
|
181
|
+
kind = item.GetType()
|
|
189
182
|
if not media and kind != "transition":
|
|
190
183
|
unsupported.append("no-plain-media-source")
|
|
191
184
|
fusion_count = item.GetFusionCompCount() if media else 0
|
|
@@ -199,7 +192,7 @@ def item_snapshot(item, timeline_id, track, validated=False, media_index=None, t
|
|
|
199
192
|
unsupported.append("take-selector-or-multicam")
|
|
200
193
|
props = media.GetClipProperty() if media else {}
|
|
201
194
|
speed, properties, fades = None, None, None
|
|
202
|
-
if
|
|
195
|
+
if media:
|
|
203
196
|
if media.GetTimeline():
|
|
204
197
|
unsupported.append("nested-or-compound-timeline")
|
|
205
198
|
if kind != "video":
|
|
@@ -258,12 +251,12 @@ def item_snapshot(item, timeline_id, track, validated=False, media_index=None, t
|
|
|
258
251
|
"sourceIn": source_in, "sourceOut": source_out,
|
|
259
252
|
"mediaPoolItemId": media.GetUniqueId() if media else None, "unsupported": unsupported,
|
|
260
253
|
"kind": kind, "speed": speed, "properties": properties, "fades": fades,
|
|
261
|
-
"enabled": item.GetClipEnabled() if
|
|
262
|
-
"sourceStartTime": item.GetSourceStartTime() if
|
|
263
|
-
"sourceEndTime": item.GetSourceEndTime() if
|
|
264
|
-
"leftOffset": item.GetLeftOffset() if
|
|
265
|
-
"rightOffset": item.GetRightOffset() if
|
|
266
|
-
"markers": item.GetMarkers() if
|
|
254
|
+
"enabled": item.GetClipEnabled() if media else None,
|
|
255
|
+
"sourceStartTime": item.GetSourceStartTime() if media else None,
|
|
256
|
+
"sourceEndTime": item.GetSourceEndTime() if media else None,
|
|
257
|
+
"leftOffset": item.GetLeftOffset() if media else None,
|
|
258
|
+
"rightOffset": item.GetRightOffset() if media else None,
|
|
259
|
+
"markers": item.GetMarkers() if media else None,
|
|
267
260
|
**({"inspectorEntry": entry} if entry else {}),
|
|
268
261
|
**({"intrinsicEntry": intrinsic_entry} if intrinsic_entry else {}),
|
|
269
262
|
}
|
|
@@ -520,9 +513,6 @@ def grade_evidence(resolve, timeline, native_items, intrinsic_ids=None):
|
|
|
520
513
|
|
|
521
514
|
@contextmanager
|
|
522
515
|
def timeline_context(resolve, timeline):
|
|
523
|
-
if resolve is None or not validated_host(resolve):
|
|
524
|
-
yield
|
|
525
|
-
return
|
|
526
516
|
project = resolve.GetProjectManager().GetCurrentProject()
|
|
527
517
|
previous = project.GetCurrentTimeline()
|
|
528
518
|
try:
|
|
@@ -538,42 +528,41 @@ def timeline_context(resolve, timeline):
|
|
|
538
528
|
await_timeline(project, previous, resolve=resolve)
|
|
539
529
|
|
|
540
530
|
|
|
541
|
-
def scan_timeline(timeline, resolve
|
|
531
|
+
def scan_timeline(timeline, resolve, grade_media_id=None):
|
|
542
532
|
with timeline_context(resolve, timeline):
|
|
543
533
|
return scan_current_timeline(timeline, resolve, grade_media_id)
|
|
544
534
|
|
|
545
535
|
|
|
546
|
-
def scan_current_timeline(timeline, resolve
|
|
547
|
-
validated = resolve is not None and validated_host(resolve)
|
|
536
|
+
def scan_current_timeline(timeline, resolve, grade_media_id=None):
|
|
548
537
|
items, native_items, paths = [], [], {}
|
|
549
538
|
media_index = None
|
|
550
539
|
for track in range(1, timeline.GetTrackCount("video") + 1):
|
|
551
540
|
native = timeline.GetItemListInTrack("video", track) or []
|
|
552
541
|
native_items.extend(native)
|
|
553
542
|
for item in native:
|
|
554
|
-
if
|
|
543
|
+
if not item.GetMediaPoolItem() and item.GetFusionCompCount() > 0 and media_index is None:
|
|
555
544
|
project = resolve.GetProjectManager().GetCurrentProject()
|
|
556
545
|
media_index = {m.GetUniqueId(): m for m in all_media(project.GetMediaPool().GetRootFolder())}
|
|
557
|
-
snapshot = item_snapshot(item, timeline.GetUniqueId(), track,
|
|
558
|
-
snapshot.update(trackEnabled=timeline.GetIsTrackEnabled("video", track)
|
|
559
|
-
trackLocked=timeline.GetIsTrackLocked("video", track)
|
|
546
|
+
snapshot = item_snapshot(item, timeline.GetUniqueId(), track, media_index, fps_value(timeline.GetSetting("timelineFrameRate")))
|
|
547
|
+
snapshot.update(trackEnabled=timeline.GetIsTrackEnabled("video", track),
|
|
548
|
+
trackLocked=timeline.GetIsTrackLocked("video", track))
|
|
560
549
|
items.append(snapshot)
|
|
561
|
-
if
|
|
550
|
+
if item.GetMediaPoolItem():
|
|
562
551
|
paths[snapshot["id"]] = item.GetMediaPoolItem().GetClipProperty("File Path") or ""
|
|
563
552
|
elif snapshot.get('intrinsicEntry'):
|
|
564
553
|
paths[snapshot['id']] = media_index[snapshot['mediaPoolItemId']].GetClipProperty('File Path') or ''
|
|
565
|
-
evidence = otio_evidence(resolve, timeline) if
|
|
566
|
-
if
|
|
554
|
+
evidence = otio_evidence(resolve, timeline) if items else {"complete": True, "reasons": [], "hash": None}
|
|
555
|
+
if items and evidence.get("clipCount") != sum(1 for item in items if item["kind"] == "video" and item["mediaPoolItemId"]):
|
|
567
556
|
evidence["reasons"] = sorted(set(evidence["reasons"] + ["otio-item-coverage-mismatch"]))
|
|
568
557
|
evidence["globalReasons"] = sorted(set(evidence.get("globalReasons", evidence["reasons"]) + ["otio-item-coverage-mismatch"]))
|
|
569
|
-
scoped_reasons = otio_item_reasons(evidence, items, paths, timeline.GetStartFrame(), fps_value(timeline.GetSetting("timelineFrameRate"))) if
|
|
558
|
+
scoped_reasons = otio_item_reasons(evidence, items, paths, timeline.GetStartFrame(), fps_value(timeline.GetSetting("timelineFrameRate"))) if items else {}
|
|
570
559
|
# A media readback retains grades only for that media's uses. Keep every
|
|
571
560
|
# native item and OTIO check for indirect references, but avoid exporting
|
|
572
561
|
# unrelated LUTs. Whole-timeline snapshots still inspect all grades.
|
|
573
562
|
grade_items = [native for native, item in zip(native_items, items)
|
|
574
563
|
if grade_media_id is None or item["mediaPoolItemId"] == grade_media_id]
|
|
575
|
-
grades = grade_evidence(resolve, timeline, grade_items, {i['id'] for i in items if i.get('intrinsicEntry')}) if
|
|
576
|
-
entry_scope = scope_for(resolve.GetProjectManager(), resolve.GetProjectManager().GetCurrentProject()) if
|
|
564
|
+
grades = grade_evidence(resolve, timeline, grade_items, {i['id'] for i in items if i.get('intrinsicEntry')}) if grade_items and any(i["kind"] == "video" for i in items) else {}
|
|
565
|
+
entry_scope = scope_for(resolve.GetProjectManager(), resolve.GetProjectManager().GetCurrentProject()) if any(i.get("inspectorEntry") or i.get('intrinsicEntry') for i in items) else None
|
|
577
566
|
for item in items:
|
|
578
567
|
item["otioHash"] = evidence["hash"]
|
|
579
568
|
item["unsupported"] = sorted(set(item["unsupported"] + scoped_reasons.get(item["id"], evidence["reasons"])))
|
|
@@ -585,10 +574,9 @@ def scan_current_timeline(timeline, resolve=None, grade_media_id=None):
|
|
|
585
574
|
owner = entry["identity"]
|
|
586
575
|
if any(owner[key] != value for key, value in entry_scope.items()):
|
|
587
576
|
item["unsupported"] = sorted(set(item["unsupported"] + ["inspector-identity-conflict"]))
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
for item in items: item["unsupported"] = sorted(set(item["unsupported"] + ["timeline-grade-unverified"]))
|
|
577
|
+
graph = timeline.GetNodeGraph()
|
|
578
|
+
if graph and any(graph.GetToolsInNode(index) for index in range(1, graph.GetNumNodes() + 1)):
|
|
579
|
+
for item in items: item["unsupported"] = sorted(set(item["unsupported"] + ["timeline-grade-unverified"]))
|
|
592
580
|
return items
|
|
593
581
|
|
|
594
582
|
|
|
@@ -650,7 +638,7 @@ def media_identity(project, scope, media):
|
|
|
650
638
|
return data
|
|
651
639
|
|
|
652
640
|
|
|
653
|
-
def media_snapshot(project, scope, media, resolve
|
|
641
|
+
def media_snapshot(project, scope, media, resolve):
|
|
654
642
|
data = media_identity(project, scope, media)
|
|
655
643
|
media_id = media.GetUniqueId()
|
|
656
644
|
uses = []
|
|
@@ -662,20 +650,20 @@ def media_snapshot(project, scope, media, resolve=None):
|
|
|
662
650
|
items = scan_timeline(timeline, resolve, grade_media_id=media_id)
|
|
663
651
|
uses.extend(i for i in items if i["mediaPoolItemId"] == media_id)
|
|
664
652
|
# A nested timeline could reference this media indirectly.
|
|
665
|
-
if any(any(reason in i["unsupported"] for reason in ("nested-or-compound-timeline", "no-plain-media-source", "nested-or-unknown-otio-item", "nested-media-reference", "otio-inspection-unavailable", "otio-item-coverage-mismatch"
|
|
653
|
+
if any(any(reason in i["unsupported"] for reason in ("nested-or-compound-timeline", "no-plain-media-source", "nested-or-unknown-otio-item", "nested-media-reference", "otio-inspection-unavailable", "otio-item-coverage-mismatch")) for i in items):
|
|
666
654
|
complete = False
|
|
667
|
-
settings = project.GetSettings()
|
|
655
|
+
settings = project.GetSettings()
|
|
668
656
|
data.update({"uses": uses, "usageScanComplete": complete,
|
|
669
657
|
"playbackPolicy": {key: settings.get(key) for key in ("perfProxyMediaMode", "perfOptimisedMediaOn", "perfRenderCacheMode")}})
|
|
670
658
|
return data
|
|
671
659
|
|
|
672
660
|
|
|
673
|
-
def timeline_snapshot(project, scope, timeline, resolve
|
|
661
|
+
def timeline_snapshot(project, scope, timeline, resolve):
|
|
674
662
|
with timeline_context(resolve, timeline):
|
|
675
663
|
return current_timeline_snapshot(project, scope, timeline, resolve)
|
|
676
664
|
|
|
677
665
|
|
|
678
|
-
def current_timeline_snapshot(project, scope, timeline, resolve
|
|
666
|
+
def current_timeline_snapshot(project, scope, timeline, resolve):
|
|
679
667
|
items = scan_timeline(timeline, resolve)
|
|
680
668
|
sources = []
|
|
681
669
|
# Rebuild once per snapshot, not once per source. Do not cache across calls:
|
|
@@ -691,8 +679,8 @@ def current_timeline_snapshot(project, scope, timeline, resolve=None):
|
|
|
691
679
|
props = media.GetClipProperty() or {}
|
|
692
680
|
sources.append({"mediaPoolItemId": media_id, "properties": {key: props.get(key) for key in ("File Path", "FPS", "Frames", "Resolution", "Input Color Space", "Input Gamma", "Data Level", "Proxy Media Path")},
|
|
693
681
|
"bridgeMetadata": (media.GetThirdPartyMetadata() or {}).get("motionharness.bridge")})
|
|
694
|
-
settings = project.GetSettings()
|
|
695
|
-
data = {"scope": scope, "timelineId": timeline.GetUniqueId(), "name": timeline.GetName(),
|
|
682
|
+
settings = project.GetSettings()
|
|
683
|
+
data = {"scope": scope, "timelineId": timeline.GetUniqueId(), "name": timeline.GetName(),
|
|
696
684
|
"fps": fps_value(timeline.GetSetting("timelineFrameRate")), "startFrame": timeline.GetStartFrame(),
|
|
697
685
|
"resolution": {"width": int(timeline.GetSetting("timelineResolutionWidth")), "height": int(timeline.GetSetting("timelineResolutionHeight"))},
|
|
698
686
|
"endFrame": timeline.GetEndFrame(), "items": items, "sources": sources,
|
|
@@ -790,7 +778,6 @@ def seek_timecode(project, timeline, requested, timeout=3.0, resolve=None):
|
|
|
790
778
|
|
|
791
779
|
|
|
792
780
|
def preview_frame(resolve, project, scope, params, before=None):
|
|
793
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该平台与构建的画面捕获尚未验证。")
|
|
794
781
|
require_active_project(resolve, project)
|
|
795
782
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "Resolve 正在导出。")
|
|
796
783
|
timeline = timeline_for(project, params["timelineId"])
|
|
@@ -847,7 +834,7 @@ def playback_policy(project):
|
|
|
847
834
|
|
|
848
835
|
|
|
849
836
|
def render_preset_evidence(resolve, project):
|
|
850
|
-
name = "
|
|
837
|
+
name = "MotionHarness Bridge delivery check " + str(uuid.uuid4())
|
|
851
838
|
require(project.SaveAsNewRenderPreset(name), "CAPABILITY_UNAVAILABLE", "无法核对导出预设的缓存策略。")
|
|
852
839
|
try:
|
|
853
840
|
with tempfile.TemporaryDirectory(prefix="mh-bridge-preset-") as directory:
|
|
@@ -861,7 +848,6 @@ def render_preset_evidence(resolve, project):
|
|
|
861
848
|
|
|
862
849
|
|
|
863
850
|
def create_render(resolve, project, scope, params):
|
|
864
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该构建尚未验证指定任务导出。")
|
|
865
851
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "Resolve 已有输出任务在运行。")
|
|
866
852
|
timeline = timeline_for(project, params["timelineId"])
|
|
867
853
|
expected = params["expectedTimelineFingerprint"]
|
|
@@ -872,7 +858,7 @@ def create_render(resolve, project, scope, params):
|
|
|
872
858
|
previous = project.GetCurrentTimeline()
|
|
873
859
|
before_jobs = project.GetRenderJobList() or []
|
|
874
860
|
require(not any(os.path.realpath(job.get("TargetDir", "")) == os.path.realpath(os.path.dirname(path)) and job.get("OutputFilename") == os.path.basename(path) for job in before_jobs), "TARGET_OCCUPIED", "输出位置已被其他队列项占用。")
|
|
875
|
-
backup = "
|
|
861
|
+
backup = "MotionHarness Bridge restore " + str(uuid.uuid4())
|
|
876
862
|
require(project.SaveAsNewRenderPreset(backup), "CAPABILITY_UNAVAILABLE", "无法保存原有导出配置,未修改设置。")
|
|
877
863
|
job_id = None
|
|
878
864
|
try:
|
|
@@ -920,7 +906,6 @@ def create_render(resolve, project, scope, params):
|
|
|
920
906
|
|
|
921
907
|
|
|
922
908
|
def start_render(resolve, project, scope, params):
|
|
923
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该构建尚未验证指定任务导出。")
|
|
924
909
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "Resolve 正在执行其他渲染;未启动队列。")
|
|
925
910
|
job = render_job(project, params["jobId"])
|
|
926
911
|
require(params.get("expectedJobFingerprint") and fingerprint(job) == params["expectedJobFingerprint"], "VERSION_CONFLICT", "队列任务的输出设置已改变或没有核对记录,未启动。")
|
|
@@ -947,7 +932,6 @@ def start_render(resolve, project, scope, params):
|
|
|
947
932
|
|
|
948
933
|
|
|
949
934
|
def finish_render(resolve, project, params):
|
|
950
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该构建尚未验证输出恢复。")
|
|
951
935
|
render_job(project, params["jobId"])
|
|
952
936
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "仍有渲染运行,保留原始画面模式直至可核对的终态。")
|
|
953
937
|
expected, actual = params["playbackPolicy"], playback_policy(project)
|
|
@@ -959,7 +943,7 @@ def finish_render(resolve, project, params):
|
|
|
959
943
|
|
|
960
944
|
|
|
961
945
|
def export_references(resolve, project, scope, params):
|
|
962
|
-
require(
|
|
946
|
+
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "输出后画面核对需要已完成渲染的目标工程。")
|
|
963
947
|
timeline = timeline_for(project, params["timelineId"])
|
|
964
948
|
before = timeline_snapshot(project, scope, timeline, resolve)
|
|
965
949
|
require(before["fingerprint"] == params["expectedTimelineFingerprint"], "VERSION_CONFLICT", "输出后的时间线已改变,不能生成原回执的参考画面。")
|
|
@@ -986,7 +970,6 @@ def export_references(resolve, project, scope, params):
|
|
|
986
970
|
|
|
987
971
|
|
|
988
972
|
def preview_original(resolve, project, scope, params):
|
|
989
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该构建尚未验证原始媒体合成检查。")
|
|
990
973
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "不能在导出期间改变合成画面检查策略。")
|
|
991
974
|
timeline = timeline_for(project, params["timelineId"])
|
|
992
975
|
before = timeline_snapshot(project, scope, timeline, resolve)
|
|
@@ -1008,8 +991,6 @@ def preview_original(resolve, project, scope, params):
|
|
|
1008
991
|
|
|
1009
992
|
|
|
1010
993
|
def selection_snapshot(resolve, plain_media_only=False):
|
|
1011
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该构建尚未验证时间线选择读取。")
|
|
1012
|
-
|
|
1013
994
|
def plain_item(item):
|
|
1014
995
|
media = item.GetMediaPoolItem()
|
|
1015
996
|
require(media is not None and media is not False, "CAPABILITY_UNAVAILABLE", "请选择有直接视频源的普通片段;生成器入口暂不支持此操作。")
|
|
@@ -1123,7 +1104,7 @@ def inspector_operation(resolve, params):
|
|
|
1123
1104
|
# A composition imported through the API is not an Edit-page Fusion effect.
|
|
1124
1105
|
# Keep the graph experiment separate from the product's Inspector entry.
|
|
1125
1106
|
require(params.get("remove") or params.get("compositionOnly") is True, "CAPABILITY_UNAVAILABLE",
|
|
1126
|
-
"ImportFusionComp 不能创建 Edit 检查器效果,请通过原生特效库应用
|
|
1107
|
+
"ImportFusionComp 不能创建 Edit 检查器效果,请通过原生特效库应用 MotionHarness Bridge 模板。")
|
|
1127
1108
|
identity = params["identity"]
|
|
1128
1109
|
require(identity.get("schemaVersion") == 1, "INVALID_REQUEST", "检查器入口身份版本无效。")
|
|
1129
1110
|
selected = selection_snapshot(resolve)
|
|
@@ -1158,7 +1139,7 @@ def inspector_operation(resolve, params):
|
|
|
1158
1139
|
before = timeline_snapshot(project, scope, timeline, resolve)
|
|
1159
1140
|
targets = [value for value in before["items"] if value["id"] == item.GetUniqueId()]
|
|
1160
1141
|
require(len(targets) == 1 and not targets[0]["unsupported"] and not targets[0].get("trackLocked"),
|
|
1161
|
-
"UNSUPPORTED_TIMELINE_ITEM", "实验入口只接受完整读回通过、未锁定的普通
|
|
1142
|
+
"UNSUPPORTED_TIMELINE_ITEM", "实验入口只接受完整读回通过、未锁定的普通 MotionHarness 片段。")
|
|
1162
1143
|
with open(params["compositionPath"], "rb") as stream:
|
|
1163
1144
|
data = stream.read()
|
|
1164
1145
|
require(hashlib.sha256(data).hexdigest() == params["compositionSha256"] and data.startswith(b"-- MH Resolve Bridge inspector v1\n"),
|
|
@@ -1199,6 +1180,8 @@ def inspector_operation(resolve, params):
|
|
|
1199
1180
|
|
|
1200
1181
|
|
|
1201
1182
|
def dispatch(resolve, operation, params):
|
|
1183
|
+
# Resolve version and process architecture are diagnostic information only.
|
|
1184
|
+
# Operations use the host APIs directly and report actual failures.
|
|
1202
1185
|
if operation == "ofx_source_target":
|
|
1203
1186
|
return ofx_source_target(resolve, params)
|
|
1204
1187
|
if operation == "selection":
|
|
@@ -1233,22 +1216,19 @@ def dispatch(resolve, operation, params):
|
|
|
1233
1216
|
"scope": scope_for(manager, project) if project else None,
|
|
1234
1217
|
"timelines": [{"id": t.GetUniqueId(), "name": t.GetName()} for t in timelines(project)] if project else [],
|
|
1235
1218
|
"adapter": "documented-python-scripting", "officialMcp": "not-tested-by-scripting",
|
|
1236
|
-
"replacement": "stable-playback-file-with-media-identity"
|
|
1237
|
-
"frameConvention": "half-open"
|
|
1238
|
-
"profile": {"platform": sys.platform, "architecture": platform.machine()
|
|
1219
|
+
"replacement": "stable-playback-file-with-media-identity",
|
|
1220
|
+
"frameConvention": "half-open",
|
|
1221
|
+
"profile": {"platform": sys.platform, "architecture": platform.machine()},
|
|
1239
1222
|
"timelineReadback": "top-level-video; native speed/properties/fades and conservative OTIO structure check"}
|
|
1240
1223
|
proof = params if operation in ("render_status", "cancel_render") else None
|
|
1241
1224
|
project, scope = project_for(resolve, params.get("scope"), proof)
|
|
1242
1225
|
if operation == "entry_target":
|
|
1243
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该 Resolve 平台与构建尚未验证所选片段管理。")
|
|
1244
1226
|
return entry_target(resolve, project, scope, params)
|
|
1245
1227
|
if operation in ("legacy_preview", "legacy_convert", "legacy_check"):
|
|
1246
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该 Resolve 平台与构建尚未验证旧生成器结构转换。")
|
|
1247
1228
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "Resolve 正在导出。")
|
|
1248
1229
|
action = {"legacy_preview": legacy.preview, "legacy_convert": legacy.convert, "legacy_check": legacy.check}[operation]
|
|
1249
1230
|
return action(resolve, project, scope, params, globals())
|
|
1250
1231
|
if operation == "duplicate_instance":
|
|
1251
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该 Resolve 平台与构建尚未验证单实例独立副本。")
|
|
1252
1232
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "Resolve 正在导出。")
|
|
1253
1233
|
return instances.duplicate_instance(resolve, project, scope, params, globals())
|
|
1254
1234
|
if operation == "timeline":
|
|
@@ -1293,8 +1273,6 @@ def dispatch(resolve, operation, params):
|
|
|
1293
1273
|
require(len(found) <= 1, "RECOVERY_CONFLICT", "多个媒体对象拥有同一绑定身份,未自动选择。")
|
|
1294
1274
|
return media_snapshot(project, scope, found[0], resolve) if found else None
|
|
1295
1275
|
if operation in ("render_status", "cancel_render"):
|
|
1296
|
-
if operation == "cancel_render":
|
|
1297
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该构建尚未验证指定任务取消。")
|
|
1298
1276
|
render_job(project, params["jobId"])
|
|
1299
1277
|
status = project.GetRenderJobStatus(params["jobId"])
|
|
1300
1278
|
if operation == "cancel_render" and status.get("JobStatus") == "Rendering":
|
|
@@ -1314,7 +1292,6 @@ def dispatch(resolve, operation, params):
|
|
|
1314
1292
|
return media_snapshot(project, scope, media, resolve)
|
|
1315
1293
|
before = media_identity(project, scope, media)
|
|
1316
1294
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "Resolve 正在导出,桥接没有修改媒体。")
|
|
1317
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该 Resolve 平台与构建尚未验证自动修改的帧边界。")
|
|
1318
1295
|
require(before["fingerprint"] == params["expectedFingerprint"], "VERSION_CONFLICT", "Resolve 原素材的身份、关联或媒体规格已变化。")
|
|
1319
1296
|
if operation == "replace":
|
|
1320
1297
|
repair = params.get("offlineRepair")
|
|
@@ -1363,7 +1340,6 @@ def dispatch(resolve, operation, params):
|
|
|
1363
1340
|
return after
|
|
1364
1341
|
if operation == "insert":
|
|
1365
1342
|
require(not project.IsRenderingInProgress(), "RESOLVE_BUSY", "Resolve 正在导出。")
|
|
1366
|
-
require(validated_host(resolve), "CAPABILITY_UNAVAILABLE", "该 Resolve 平台与构建尚未验证插入的帧边界。")
|
|
1367
1343
|
timeline = timeline_for(project, params["timelineId"])
|
|
1368
1344
|
before = timeline_snapshot(project, scope, timeline, resolve)
|
|
1369
1345
|
require(before["fingerprint"] == params["expectedTimelineFingerprint"], "VERSION_CONFLICT", "时间线在插入前发生变化。")
|
|
@@ -1419,7 +1395,7 @@ def dispatch(resolve, operation, params):
|
|
|
1419
1395
|
"mediaType": 1, "trackIndex": track, "recordFrame": record}]) or []
|
|
1420
1396
|
require(len(created) == 1, "APPLY_OUTCOME_UNKNOWN", "Resolve 未返回唯一插入对象,需核对后恢复。")
|
|
1421
1397
|
save_insert_progress(params, before, "appended", media, created[0])
|
|
1422
|
-
item = item_snapshot(created[0], timeline.GetUniqueId(), track,
|
|
1398
|
+
item = item_snapshot(created[0], timeline.GetUniqueId(), track, {media.GetUniqueId(): media}, before["fps"])
|
|
1423
1399
|
sampling_matches = item["sourceIn"] == start and item["sourceOut"] == end
|
|
1424
1400
|
if spec["fps"] != before["fps"]:
|
|
1425
1401
|
expected_start = start * spec["fps"]["den"] / spec["fps"]["num"]
|