Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions app/services/director_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,22 @@ def persist_pipeline_output_timing(
pass


def _write_director_assembly_sidecar(
final_path: str,
sidecar: Mapping[str, Any],
workspace_id: Optional[str] = None,
) -> None:
"""Publish a v1 asset manifest for the assembled Director video."""
from services.asset_manifest import publish_generation_sidecar

publish_generation_sidecar(
final_path,
sidecar,
workspace_id=workspace_id,
tool="director",
)


def _final_pipeline_output_name(output_files: list[str]) -> str:
"""Choose the assembled deliverable from a Director output list."""
names = [str(name or "") for name in output_files if str(name or "")]
Expand Down Expand Up @@ -13426,8 +13442,7 @@ def _run_minimax_h3_story_video(
"result_kind": result_kind,
"created_at": time.time(),
}
with open(os.path.splitext(final_path)[0] + ".meta.json", "w", encoding="utf-8") as handle:
json.dump(sidecar, handle, indent=2)
_write_director_assembly_sidecar(final_path, sidecar, workspace)
return [*outputs, final_name]


Expand Down
55 changes: 55 additions & 0 deletions tests/test_director_pipeline_timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest.mock import patch

from app.services import director_pipeline
from app.services.asset_manifest import SCHEMA_NAME, read_asset_manifest


def test_pipeline_timing_metadata_normalizes_live_terminal_state():
Expand Down Expand Up @@ -177,3 +178,57 @@ def test_pipeline_stage_times_accumulate_across_resumed_work():
assert pipeline["updated_at"] == 150.0
finally:
director_pipeline._pipelines = original_pipelines


def test_director_assembly_sidecar_publishes_canonical_manifest(tmp_path: Path):
first = tmp_path / "minimax_h3_pipe-22_multiclip.mp4"
second = tmp_path / "minimax_h3_pipe-22_multiclip-b.mp4"
first.write_bytes(b"video-a")
second.write_bytes(b"video-b")
sidecar = {
"params": {
"model_type": "minimax_h3",
"resolution": "960x544",
"source_clips": ["clip-a.mp4", "clip-b.mp4"],
"director_pipeline_id": "pipe-22",
"pipeline_type": "short_film_story",
"production_kind": "story",
"result_kind": "video",
"director_generation_mode": "direct_video",
"direct_video_master_prompt": "a joined short",
"api_key": "secret",
},
"generation_mode": "video",
"result_kind": "video",
"created_at": 1_700_000_100,
}

director_pipeline._write_director_assembly_sidecar(
str(first), sidecar, "night-shift",
)
published = tmp_path / "minimax_h3_pipe-22_multiclip.meta.json"
raw = json.loads(published.read_text(encoding="utf-8"))
loaded = read_asset_manifest(first, workspace_id="night-shift")

assert raw["schema"] == SCHEMA_NAME
assert raw["params"]["director_pipeline_id"] == "pipe-22"
assert raw["generation_mode"] == "video"
assert raw["result_kind"] == "video"
assert "secret" not in published.read_text(encoding="utf-8")
assert loaded is not None
assert loaded["origin"]["tool"] == "director"
assert loaded["origin"]["actor"] == "unknown"
assert loaded["origin"]["workspace_id"] == "night-shift"
assert loaded["origin"].get("project") is None
assert loaded["origin"].get("production") is None
assert loaded["execution"]["pipeline_id"] == "pipe-22"
first_id = loaded["asset"]["id"]

director_pipeline._write_director_assembly_sidecar(
str(first), sidecar, "night-shift",
)
director_pipeline._write_director_assembly_sidecar(
str(second), sidecar, "night-shift",
)
assert read_asset_manifest(first, workspace_id="night-shift")["asset"]["id"] == first_id
assert read_asset_manifest(second, workspace_id="night-shift")["asset"]["id"] != first_id