修复 External 去背景重放与画布竞态
按原始请求指纹在可变预检前返回幂等任务 修正来源身份优先级与跨项目歧义校验 冻结并复验原位替换目标的完整媒体语义 修复 Python helper 原位替换请求并补正向路由测试
This commit is contained in:
@@ -119,7 +119,7 @@ client.remove_background(
|
||||
)
|
||||
```
|
||||
|
||||
Background removal preserves the source pixel size. When `canvasSession` is used, pass the real `source_width` and `source_height`, or provide both `canvasWidth` and `canvasHeight`; the helper rejects missing dimensions instead of guessing a square placeholder. `assetKind` may only describe a static image and must match the authoritative source record. Prefer a project resource ID or asset ID when the same object key has multiple semantic registrations; for a raw object key outside in-place replacement, pass `sourceResourceId` to disambiguate. Without `canvasCompletion`, `targetLayerId` may replace only the layer that points to the same authoritative object as the source, and the server durably binds a raw object key to that target resource for Worker revalidation.
|
||||
Background removal preserves the source pixel size. For normal canvas placement with `canvasSession`, pass the real `source_width` and `source_height`, or provide both `canvasWidth` and `canvasHeight`; the helper rejects missing dimensions instead of guessing a square placeholder. `assetKind` may only describe a static image and must match the authoritative source record. Prefer a project resource ID or asset ID when the same object key has multiple semantic registrations; for a raw object key outside in-place replacement, pass `sourceResourceId` to disambiguate. Passing `targetLayerId` selects in-place replacement: the helper retains the session's project/library context but does not inject `canvasCompletion`, and it rejects an explicit `canvasCompletion` combined with `targetLayerId`. The target layer must point to the same authoritative object as the source, and the server durably binds a raw object key to that target resource for Worker revalidation.
|
||||
|
||||
Helper convenience methods wait locally, but the server still uses short asynchronous submit/status requests. For durable caller-controlled orchestration, call `submit_generation`, persist its `operationId` and idempotency key, then call `get_generation` or `wait_for_generation`.
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ A minimal `canvasCompletion` is:
|
||||
|
||||
`dialogId` is optional. The placeholder supplies canvas placement and completion coordinates; it is not a final media pixel-size constraint. For successful pixel-art snapping, the result layer uses the final logical-grid PNG dimensions even when they differ from the placeholder. Do not reconstruct canvas state from completion results. Reload the project and asset library when complete authoritative snapshots are needed.
|
||||
|
||||
Background removal preserves the source image dimensions. The Python helper therefore requires the real `source_width` and `source_height` whenever `canvasSession` is used without an explicit `canvasWidth` plus `canvasHeight`; it never substitutes a square default. The request `assetKind` is optional, static-image only, and must equal the authoritative source type when one exists. Without `canvasCompletion`, an in-place `targetLayerId` must resolve to the same authoritative source object; a raw object key is bound to that target resource instead of relying on project-list order.
|
||||
Background removal preserves the source image dimensions. For normal canvas placement, the Python helper therefore requires the real `source_width` and `source_height` whenever `canvasSession` is used without an explicit `canvasWidth` plus `canvasHeight`; it never substitutes a square default. Passing `targetLayerId` instead selects in-place replacement, so the helper keeps the session's project/library fields without injecting `canvasCompletion` and rejects callers that explicitly combine both placement modes. The request `assetKind` is optional, static-image only, and must equal the authoritative source type when one exists. An in-place target must resolve to the same authoritative source object; a raw object key is bound to that target resource instead of relying on project-list order.
|
||||
|
||||
Character animation accepts `assetFolderId` and `assetLabel` and persists the final transparent sequence directly. Its completed compact result includes the authoritative `assetKind="character-animation"` resource and asset with `imageSequenceFrames` and `imageSequenceDurationMs`. Use those records directly and never synthesize a duplicate asset from the first frame.
|
||||
|
||||
|
||||
@@ -582,11 +582,16 @@ class GenarrativeExternalClient:
|
||||
session = fields.get("canvasSession")
|
||||
if session is None:
|
||||
session = fields.get("canvas_session")
|
||||
target_layer_id = normalize_optional_text(fields.get("targetLayerId"))
|
||||
if target_layer_id and fields.get("canvasCompletion") is not None:
|
||||
raise GenarrativeApiError(
|
||||
"targetLayerId and canvasCompletion are mutually exclusive for background removal"
|
||||
)
|
||||
canvas_width = fields.get("canvasWidth")
|
||||
canvas_height = fields.get("canvasHeight")
|
||||
if (canvas_width is None) != (canvas_height is None):
|
||||
raise GenarrativeApiError("canvasWidth and canvasHeight must be provided together")
|
||||
if session is not None and canvas_width is None:
|
||||
if session is not None and canvas_width is None and not target_layer_id:
|
||||
if source_width is None or source_height is None:
|
||||
raise GenarrativeApiError(
|
||||
"remove_background requires source_width and source_height when canvasSession is used without canvasWidth/canvasHeight"
|
||||
@@ -599,6 +604,8 @@ class GenarrativeExternalClient:
|
||||
source_width or 1,
|
||||
source_height or 1,
|
||||
)
|
||||
if target_layer_id:
|
||||
fields.pop("canvasCompletion", None)
|
||||
idempotency_key = fields.pop("idempotencyKey", None)
|
||||
return self.submit_and_wait_generation(
|
||||
"/api/external/v1/editor/images/background-removals",
|
||||
@@ -838,6 +845,30 @@ def _self_test() -> None:
|
||||
assert calls[0]["body"]["canvasCompletion"]["placeholder"]["width"] == 720
|
||||
assert calls[0]["body"]["canvasCompletion"]["placeholder"]["height"] == 1280
|
||||
calls.clear()
|
||||
client.remove_background(
|
||||
"uploads/source.png",
|
||||
canvasSession=session,
|
||||
targetLayerId="layer-1",
|
||||
assetLabel="原位去背景结果",
|
||||
)
|
||||
assert calls[0]["body"]["projectId"] == "proj-demo"
|
||||
assert calls[0]["body"]["assetFolderId"] == "editor-asset-folder-demo"
|
||||
assert calls[0]["body"]["assetLabel"] == "原位去背景结果"
|
||||
assert calls[0]["body"]["targetLayerId"] == "layer-1"
|
||||
assert "canvasCompletion" not in calls[0]["body"]
|
||||
calls.clear()
|
||||
try:
|
||||
client.remove_background(
|
||||
"uploads/source.png",
|
||||
canvasSession=session,
|
||||
targetLayerId="layer-1",
|
||||
canvasCompletion={"title": "冲突完成指令"},
|
||||
)
|
||||
except GenarrativeApiError as error:
|
||||
assert "targetLayerId and canvasCompletion are mutually exclusive" in str(error)
|
||||
else:
|
||||
raise AssertionError("background removal must reject conflicting canvas placement modes")
|
||||
assert calls == []
|
||||
try:
|
||||
client.remove_background("uploads/source.png", canvasSession=session)
|
||||
except GenarrativeApiError as error:
|
||||
|
||||
Reference in New Issue
Block a user