mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-20 08:38:05 +02:00
* feat: bundle standalone taste distillation and application workflows * docs: fix imported taste skill markdown lint * docs: align Turkish agent catalog with taste skills * refactor: make ECC the canonical reusable video engine * fix: preserve video duration when applying image overlays * fix: preserve background colors in image compositing * fix: report best-effort duration targets and shortfalls * feat: ship verified Fusion presets with compatibility provenance * feat(tasteforge): preserve native edits in application bundles * feat(tasteforge): compile local preservation without hosted input * fix: update js-yaml to patched 4.3.2 * test: report bounded Stop wrapper failure diagnostics * fix(tasteforge): fail closed on unsafe output names, missing overlays and cadence - cli: default report and spec paths are derived from pack name and profile genre; require the manifest's name pattern before using either as a filename part so a traversal string cannot write outside cwd/out. - apply_local: a pack without cadence.json, or with no measured shots and no explicit mean_shot, raises instead of silently planning 1.0s shots and reporting a measured cadence. - legacy apply: a missing overlay aborts before any paid upload; forge() would have rejected it after every take was generated. - requirements-live: pin fal-client>=0.13.0, the first release whose subscribe() accepts client_timeout. Addresses the five P1 findings from the independent review of #3033. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015fxHRsydPqEcYngGbqkgt1 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
170 lines
5.7 KiB
Python
170 lines
5.7 KiB
Python
"""Distillation: profile (+ pack measurements) -> structured style spec.
|
|
|
|
Two paths, one boundary:
|
|
|
|
* :func:`distill_local` - deterministic, offline, dry-run semantics. It maps a
|
|
local interview profile onto the spec contract, merges measured grounding
|
|
when a pack supplies it, and stamps ``dry_run: true`` / ``provider: none``.
|
|
It never pretends a vision model ran.
|
|
* :func:`distill_live` - FAILS CLOSED. Live (provider) distillation requires
|
|
explicit separately authorized execution, which this package never grants.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
from . import pack as pack_mod
|
|
from . import schema
|
|
|
|
__all__ = [
|
|
"ProviderDisabledError",
|
|
"distill_local",
|
|
"distill_live",
|
|
"grounding_from_grade",
|
|
"build_spec",
|
|
]
|
|
|
|
_SPEC_STRING_KEYS = (
|
|
"palette_description", "grain", "lighting", "focal_length",
|
|
"camera_motion", "subject_framing", "grade_description",
|
|
)
|
|
_SPEC_LIST_KEYS = ("mood_adjectives", "avoid")
|
|
|
|
# Interview answer id -> spec key (palette -> palette_description).
|
|
_KEY_MAP = {
|
|
"palette": "palette_description",
|
|
**{k: k for k in _SPEC_STRING_KEYS + _SPEC_LIST_KEYS if k != "palette_description"},
|
|
}
|
|
|
|
|
|
class ProviderDisabledError(RuntimeError):
|
|
"""Live provider distillation was requested but is not authorized."""
|
|
|
|
|
|
_FAIL_CLOSED = (
|
|
"live distillation requires explicit separately authorized execution; "
|
|
"this package ships no provider adapters and performs no network calls. "
|
|
"Use distill_local() (offline, deterministic, dry-run) instead."
|
|
)
|
|
|
|
|
|
def _utc_now() -> str:
|
|
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
|
|
def build_spec(look: dict[str, Any], *, pack_name: str | None = None,
|
|
grounding_used: bool = False) -> dict[str, Any]:
|
|
"""Assemble a schema-valid spec from look constraints (offline)."""
|
|
spec: dict[str, Any] = {}
|
|
for key in _SPEC_STRING_KEYS:
|
|
value = look.get(key)
|
|
spec[key] = value.strip() if isinstance(value, str) else ""
|
|
for key in _SPEC_LIST_KEYS:
|
|
value = look.get(key)
|
|
spec[key] = [str(v) for v in value] if isinstance(value, list) else []
|
|
|
|
spec["source"] = {
|
|
"pack": pack_name or "",
|
|
"generated": _utc_now(),
|
|
"dry_run": True,
|
|
"provider": "none",
|
|
"grounding_used": grounding_used,
|
|
}
|
|
problems = schema.validate(spec, schema.SPEC_SCHEMA)
|
|
if problems:
|
|
raise ValueError(f"built an invalid spec: {problems}")
|
|
return spec
|
|
|
|
|
|
def distill_local(profile: dict[str, Any],
|
|
sp: pack_mod.StylePack | None = None) -> dict[str, Any]:
|
|
"""Deterministic offline distillation of a taste profile.
|
|
|
|
The output carries dry-run semantics from the recovered implementation:
|
|
``dry_run: true`` and ``provider: none`` mean no vision model ran and no
|
|
claim of live distillation is made. When a pack with grade/cadence
|
|
metadata is supplied, its measured ground truth is embedded for the
|
|
operator (and any future authorized VLM call) to consume.
|
|
"""
|
|
look = dict(profile.get("constraints", {}).get("look", {}))
|
|
|
|
grounding_used = False
|
|
grounding_text = ""
|
|
if sp is not None:
|
|
grade = sp.read_json(sp.grade_path)
|
|
cadence = sp.read_json(sp.cadence_path)
|
|
grounding_text = grounding_from_grade(grade, cadence)
|
|
grounding_used = bool(grounding_text)
|
|
|
|
spec = build_spec(
|
|
look,
|
|
pack_name=sp.name if sp is not None else profile.get("genre"),
|
|
grounding_used=grounding_used,
|
|
)
|
|
if grounding_text:
|
|
spec["grounding"] = grounding_text
|
|
return spec
|
|
|
|
|
|
def distill_live(profile: dict[str, Any]) -> dict[str, Any]:
|
|
"""Refuse live provider distillation. Fails closed, always."""
|
|
raise ProviderDisabledError(_FAIL_CLOSED)
|
|
|
|
|
|
def grounding_from_grade(grade: dict[str, Any], cadence: dict[str, Any]) -> str:
|
|
"""Measured ground truth as a factual preamble (canonicalized port).
|
|
|
|
Numeric facts belong here, not in the model's judgment: the first
|
|
ungrounded run of the recovered pipeline produced a spec asserting "no
|
|
apparent color grading" for a reference measuring a*+24.9 in the
|
|
midtones. Stating measurements as facts inverts the dependency.
|
|
"""
|
|
if not grade:
|
|
return ""
|
|
|
|
lines = [
|
|
"MEASURED GROUND TRUTH for this reference set, from numeric analysis "
|
|
"of the sampled frames. These are FACTS. Do not contradict them. Do "
|
|
"not describe this footage as neutral, ungraded, or clinical:",
|
|
]
|
|
|
|
bp, wp = grade.get("black_point"), grade.get("white_point")
|
|
if bp is not None and wp is not None:
|
|
lines.append(
|
|
f"- black point L*{bp:.1f}, white point L*{wp:.1f}, "
|
|
f"contrast (std L*) {grade.get('contrast', 0):.1f}"
|
|
)
|
|
|
|
zones = grade.get("zones") or []
|
|
if zones:
|
|
centers = [7.5, 25, 45, 65, 87.5]
|
|
z = " | ".join(
|
|
f"L*{c:.0f} a*{v[0]:+.1f} b*{v[2]:+.1f}"
|
|
for c, v in zip(centers, zones)
|
|
)
|
|
lines.append(f"- chroma by luminance zone: {z}")
|
|
|
|
pal = grade.get("palette") or []
|
|
if pal:
|
|
lines.append(
|
|
"- dominant palette: " + ", ".join(h for h, _ in pal[:5])
|
|
)
|
|
|
|
if grade.get("noise_sigma") is not None:
|
|
lines.append(
|
|
f"- measured grain sigma {grade['noise_sigma']:.4f} (encode noise, "
|
|
"not necessarily aesthetic grain)"
|
|
)
|
|
|
|
if cadence:
|
|
lines.append(
|
|
f"- cut rhythm: {cadence.get('n_shots', 0)} shots, mean "
|
|
f"{cadence.get('mean_shot', 0):.2f}s, "
|
|
f"{cadence.get('cuts_per_min', 0):.0f} cuts/min, rhythm variance "
|
|
f"{cadence.get('rhythm_variance', 0):.2f}"
|
|
)
|
|
|
|
return "\n".join(lines)
|