Skip to content

feat(execution): attach media metadata to output entries in executed/history payloads - #15417

Merged
jtydhr88 merged 2 commits into
feat/asset-video-media-metadatafrom
synap5e/feat/output-media-metadata
Aug 8, 2026
Merged

feat(execution): attach media metadata to output entries in executed/history payloads#15417
jtydhr88 merged 2 commits into
feat/asset-video-media-metadatafrom
synap5e/feat/output-media-metadata

Conversation

@synap5e

@synap5e synap5e commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

TL;DR

Reviewer attention: Worth a closer look at two decisions: the enrichment being ungated (runs without --enable-assets) and the entry key name metadata.

PR Justification: ComfyUI already knows every output's media properties, but the executed message and /history only carry {filename, subfolder, type} — so anything consuming those payloads must re-open and probe the files, which consumers that don't share the server's filesystem cannot do at all.

Stakes: Medium. Runs once per produced output on the execution path; extraction is header-only (no frame decode) and per-entry errors are swallowed, so the worst credible failure is a missing metadata key, not a broken run.

Changes: New enrich_output_with_media_metadata attaches entry["metadata"] = {kind, width, height[, duration, fps, frame_count]} at output-processing time, reusing #15180's extractors; the path-containment guard is shared with asset enrichment.

Description

Stacked on #15180 (base branch feat/asset-video-media-metadata) — it reuses extract_media_metadata from that PR.

Output entries in the executed websocket message and in /history now carry a nested media-properties object:

{
  "filename": "video_00001_.mp4",
  "subfolder": "video",
  "type": "output",
  "metadata": {
    "kind": "video",
    "width": 1280,
    "height": 720,
    "duration": 4.0,
    "fps": 24.0,
    "frame_count": 96
  }
}

Design notes:

  • Not gated on --enable-assets. feat(assets): extract video metadata into system_metadata on ingest and scan #15180 lands these properties in the asset DB; this PR is for every consumer that doesn't run the assets system — the executed payload and /history are the only interface many clients have to output files. The image half of this shape (metadata: {kind, width, height}) is already an established convention in the assets API, so consumers get one shape everywhere.
  • Runs at output-processing time, next to the existing asset-id enrichment, so the values flow into ui_outputs/the cache/history even when no client is connected, and cached-output resends carry them for free.
  • Additive and best-effort. Entries that already have a metadata key are left untouched; non-media files, unreadable headers, and per-entry errors leave the entry unchanged. Old frontends ignore the unknown key.
  • The path-containment guard moved from asset_enrichment.py into the new module and is shared by both enrichers, now with symlinks resolved (realpath) before the containment check and string-typed entry fields required.

Review responses (kept as-is)

  • Synchronous extraction on the async execute path: header-only reads are bounded and strictly lighter than the adjacent asset enrichment on this same path, which blake3-hashes entire output files synchronously. If event-loop offloading is wanted it should cover both enrichers together as a follow-up, not just this one.
  • MIME type guessed from the filename extension: deliberate parity with the asset system's own ingest/scan detection (mimetypes.guess_type), so the same files get metadata in both paths; the failure mode is a missing optional field, never a crash.

How tested

  • tests-unit/execution_test/test_media_enrichment.py (new, 17 cases): attach/shape, mime-type dispatch, no-mutation of originals, pre-existing metadata key preserved, traversal + absolute-path entries skipped, real-filesystem symlink escape/containment, non-string fields, extractor-missing / non-ImportError import failure / extractor-error degradation, multi-key outputs.
  • tests-unit/execution_test/test_enrich_output.py (existing 15 cases) still passes against the shared path guard.
  • ruff check clean on changed files.

Enrich file-type output entries with a nested metadata object
({kind, width, height} for images, plus {duration, fps, frame_count}
for videos) at output-processing time, reusing the extractors from the
asset system's media_metadata service. Unlike asset-id enrichment this
is not gated on --enable-assets: the properties flow into the executed
websocket message and /history for every consumer, including ones
without the assets system enabled.

The path containment guard moves to media_enrichment.py and is shared
with asset_enrichment.py.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@synap5e synap5e added the cursor-review Trigger multi-model Cursor code review label Aug 8, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Cursor Review — Consolidated panel

Triggered by @synap5e.

Found 5 finding(s).

Severity Count
🟡 Medium 3
🟢 Low 2

Panel: 8/8 reviewers contributed findings.

Comment thread comfy_execution/media_enrichment.py Outdated
Comment thread execution.py
# added fields flow into ui_outputs and the cache alongside the
# raw entries, even when no client is connected. Media metadata is
# attached unconditionally; asset ids only under --enable-assets.
output_ui = enrich_output_with_media_metadata(output_ui)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium — enrich_output_with_media_metadata runs synchronously inside the async execute path on every produced output, unconditionally (not gated on --enable-assets, even with no client connected), invoking PIL/PyAV header parsing with no size or time bound. Large or numerous outputs — or a crafted/truncated media file that makes PyAV probe a large region — block the asyncio event loop and stall concurrent tasks and websocket traffic. Consider offloading extraction to a thread executor. Raised by 3 of 8 reviewers (gemini-3.1-pro adversarial, kimi-k2.7-code adversarial, claude-opus-4-8-thinking-max adversarial).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept as-is — rationale in the PR description ("Review responses").

Comment thread comfy_execution/media_enrichment.py Outdated
if isinstance(entry, dict) and "metadata" not in entry:
abs_path = resolve_output_entry_path(entry)
if abs_path is not None:
mime_type = mimetypes.guess_type(entry["filename"], strict=False)[0]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low — MIME type is guessed solely from the filename extension and extract_media_metadata returns nothing when it is falsy, so valid outputs silently get no metadata whenever mimetypes lacks the extension (e.g. .webp on minimal containers without /etc/mime.types) and any explicit format field already on the entry is ignored. Extensionless or custom-suffixed media are skipped and a mismatched extension can select the wrong extractor. Impact is limited to a missing best-effort field (no crash). Raised by 2 of 8 reviewers (gpt-5.6-sol-max edge-case, claude-opus-4-8-thinking-max edge-case).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept as-is — rationale in the PR description ("Review responses").

Comment thread comfy_execution/media_enrichment.py Outdated
- Resolve symlinks (realpath on base and candidate) before the
  containment check, so a symlink planted inside output/ can't smuggle
  an outside target past commonpath; also covers output dirs that are
  themselves symlinks. Applies to both enrichers via the shared helper.
- Validate filename/subfolder/type are strings before path resolution
  so a malformed entry skips cleanly instead of raising TypeError.
- Broaden the lazy-import guard beyond ImportError: the enrichment is
  called unguarded on the output path and must degrade to a no-op when
  a dependency fails to import for any reason.
- Tests: real-filesystem symlink escape/containment, non-string fields,
  non-ImportError import failure.
@jtydhr88
jtydhr88 merged commit ba3381a into feat/asset-video-media-metadata Aug 8, 2026
8 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

cursor-review Trigger multi-model Cursor code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants