@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.
@@ -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 (render_proof and requested and not manager.GetCurrentDatabase()
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, validated=False, media_index=None, timeline_fps=None):
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 validated and not media else None
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 = [] if validated else ["host-build-and-frame-conventions-unverified"]
188
- kind = item.GetType() if validated else "unknown"
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 validated and media:
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 validated and media else None,
262
- "sourceStartTime": item.GetSourceStartTime() if validated and media else None,
263
- "sourceEndTime": item.GetSourceEndTime() if validated and media else None,
264
- "leftOffset": item.GetLeftOffset() if validated and media else None,
265
- "rightOffset": item.GetRightOffset() if validated and media else None,
266
- "markers": item.GetMarkers() if validated and media else None,
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=None, grade_media_id=None):
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=None, grade_media_id=None):
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 validated and not item.GetMediaPoolItem() and item.GetFusionCompCount() > 0 and media_index is None:
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, validated, media_index, fps_value(timeline.GetSetting("timelineFrameRate")) if validated else None)
558
- snapshot.update(trackEnabled=timeline.GetIsTrackEnabled("video", track) if validated else None,
559
- trackLocked=timeline.GetIsTrackLocked("video", track) if validated else None)
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 validated and item.GetMediaPoolItem():
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 validated and items else {"complete": not items, "reasons": [], "hash": None}
566
- if validated and items and evidence.get("clipCount") != sum(1 for item in items if item["kind"] == "video" and item["mediaPoolItemId"]):
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 validated and items else {}
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 validated and grade_items and any(i["kind"] == "video" for i in items) else {}
576
- entry_scope = scope_for(resolve.GetProjectManager(), resolve.GetProjectManager().GetCurrentProject()) if validated and any(i.get("inspectorEntry") or i.get('intrinsicEntry') for i in items) else None
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
- if validated:
589
- graph = timeline.GetNodeGraph()
590
- if graph and any(graph.GetToolsInNode(index) for index in range(1, graph.GetNumNodes() + 1)):
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=None):
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", "host-build-and-frame-conventions-unverified")) for i in items):
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() if resolve and validated_host(resolve) else {}
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=None):
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=None):
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() if resolve and validated_host(resolve) else {}
695
- data = {"scope": scope, "timelineId": timeline.GetUniqueId(), "name": timeline.GetName(), "validatedHost": bool(resolve and validated_host(resolve)),
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 = "Motion Harness Bridge delivery check " + str(uuid.uuid4())
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 = "Motion Harness Bridge restore " + str(uuid.uuid4())
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(validated_host(resolve) and not project.IsRenderingInProgress(), "RESOLVE_BUSY", "输出后画面核对需要已完成渲染的目标工程。")
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 检查器效果,请通过原生特效库应用 Motion Harness Bridge 模板。")
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", "实验入口只接受完整读回通过、未锁定的普通 Motion Harness 片段。")
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" if validated_host(resolve) else "host-build-unverified",
1237
- "frameConvention": "half-open" if validated_host(resolve) else "unverified",
1238
- "profile": {"platform": sys.platform, "architecture": platform.machine(), "basicBridgeValidated": validated_host(resolve)},
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, True, {media.GetUniqueId(): media}, before["fps"])
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"]