mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-21 09:08:01 +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>
285 lines
15 KiB
Markdown
285 lines
15 KiB
Markdown
# ECC reusable TasteForge engine
|
|
|
|
Install with `python3 -m pip install ./skills/taste-application/scripts` from an ECC checkout or extracted npm package. The Python distribution is `ecc-tasteforge`; the CLI remains `python3 -m tasteforge`. Ito-video consumes this package as an example project.
|
|
|
|
## Repeatable taste-driven video workflow
|
|
|
|
A stdlib-only Python package (no numpy/opencv/network dependencies) that
|
|
canonicalizes the recovered TasteForge flow into maintained, testable
|
|
tooling. Provider integrations (Fal) are optional adapters that **fail
|
|
closed**; every command here runs offline and deterministically. See
|
|
the skill's `SOURCE.md` for recovered-source lineage.
|
|
|
|
## Install
|
|
|
|
Requires Python 3.9 or newer. Install the `ecc-tasteforge` distribution from
|
|
ECC as shown above, then run the CLI from any directory. Core commands have
|
|
no third-party runtime dependencies. Optional media helpers use FFmpeg and
|
|
the libraries listed in the skill instructions.
|
|
|
|
## CLI
|
|
|
|
```bash
|
|
python3 -m tasteforge provenance # recovered-source lineage as JSON
|
|
python3 -m tasteforge inspect <pack-dir> # validate + summarize a style pack
|
|
python3 -m tasteforge validate <pack-dir> # exit 0 valid / 1 invalid
|
|
python3 -m tasteforge interview --answers a.json --genre NAME [--out profile.json]
|
|
python3 -m tasteforge distill --profile profile.json [--pack <pack-dir>] [--out spec.json]
|
|
python3 -m tasteforge apply --pack <pack-dir> --media media.json [--duration 20] [--out report.json]
|
|
python3 -m tasteforge apply --pack <pack-dir> --media selects.json --duration 20 --fps 30 --no-repeat --out report.json
|
|
python3 -m tasteforge export --events events.json [--out-dir out] [--fps 24] [--title cut]
|
|
python3 -m tasteforge multimodal --config workflow.json --out-dir out/multimodal
|
|
```
|
|
|
|
`--live` on `distill`/`apply` is refused (exit 2): provider generation
|
|
requires explicit separately authorized execution outside this package.
|
|
|
|
Input shapes:
|
|
|
|
- answers: `{"<question-id>": "<free text>", ...}` — ids are listed by
|
|
`tasteforge.interview.QUESTIONS` (palette, grain, lighting, focal_length,
|
|
camera_motion, subject_framing, grade_description, mood_adjectives, avoid,
|
|
brief).
|
|
- media/events: `{"clips": [{"path": "...", "duration": 6.2, "name": "..."}]}`.
|
|
|
|
Outputs:
|
|
|
|
- `interview` → taste profile (schema `TASTE_PROFILE_SCHEMA`)
|
|
- `distill` → style spec (schema `SPEC_SCHEMA`, always `dry_run: true`,
|
|
`provider: "none"`) with measured grounding embedded when a pack is given
|
|
- `apply` → application report (schema `APPLICATION_REPORT_SCHEMA`; provider
|
|
enum-locked to `"none"`) with planned shots and frame-exact timeline events
|
|
- `export` → CMX3600 `<title>.edl` + FCPXML 1.9 `<title>.fcpxml` with
|
|
rational, frame-quantised times (NTSC-safe)
|
|
- `multimodal` → distinct numbered genre specs, separate image/video/3D-asset
|
|
request manifests, a seeded aperiodic Resolve effect recipe, and a receipt
|
|
that binds every emitted artifact by relative path, byte size, SHA-256,
|
|
genre, modality, `provider_execution: false`, and exact reference/time
|
|
provenance. It requires local `ffprobe` and `ffmpeg` for measured media
|
|
features and never submits a request.
|
|
|
|
### Real-footage application
|
|
|
|
Use `--no-repeat` when each source must appear at most once. Strict mode uses
|
|
normalized source paths in manifest order, requires enough unique reviewed
|
|
clips for the cadence plan, and rejects selected sources shorter than their
|
|
assigned shots. It fills the target in output frames or fails. This mode does
|
|
not yet support separate in/out ranges from the same recording. Without the
|
|
flag, legacy round-robin selection remains available and can repeat sources.
|
|
|
|
Set `--fps` explicitly for the output sequence. It overrides the reference
|
|
pack's cadence frame rate. TasteForge plans cuts; it does not rank footage by
|
|
visual quality, apply grades or overlays, detect subjects, or import Resolve
|
|
projects. A multimodal subject-anchor descriptor is a tracking requirement,
|
|
not a completed track.
|
|
|
|
To export an application report, adapt its events to the export CLI's input
|
|
shape and keep the same output frame rate:
|
|
|
|
```bash
|
|
python3 - <<'PY'
|
|
import json
|
|
from pathlib import Path
|
|
report = json.loads(Path("report.json").read_text())
|
|
Path("events.json").write_text(json.dumps({"clips": report["timeline_events"]}))
|
|
PY
|
|
python3 -m tasteforge export --events events.json --fps 30 --out-dir out --title review-cut
|
|
```
|
|
|
|
Verify event count, total frames, source uniqueness, and media linkage before
|
|
NLE import. The exported timeline is an editable cut plan, not a rendered or
|
|
creatively approved video.
|
|
|
|
The multimodal JSON contract has `schema_version`, `run_id`, integer `seed`,
|
|
optional `evidence_files`, and `genres`. Each genre has a distinct `number`,
|
|
`slug`, `label`, local `references`, and non-empty `signature` lists for
|
|
`materials`, `motion`, `composition`, and `avoid`. Relative input paths resolve
|
|
from the config file's directory. Run output through `validate_bundle`; missing
|
|
modalities, genericized genres, periodic schedules, unanchored CV effects,
|
|
unsafe placement, unbound/tampered artifacts, or any provider-execution flag
|
|
fail closed.
|
|
|
|
## Offline fixture
|
|
|
|
`tasteforge/fixtures/flashethereal/` is recovered pack metadata
|
|
(`pack.json`, `grade.json`, `cadence.json`, `spec.json`, `grounding.txt`,
|
|
`flashethereal-cut.edl`), byte-identical
|
|
to the latest recovered generation. It exercises the full offline path with
|
|
no provider and no media.
|
|
|
|
```bash
|
|
python3 -m tasteforge inspect tasteforge/fixtures/flashethereal
|
|
```
|
|
|
|
## Library
|
|
|
|
```python
|
|
from tasteforge import pack, interview, distill, apply, export, provenance, schema
|
|
|
|
sp = pack.load("tasteforge/fixtures/flashethereal")
|
|
report = apply.apply_local(sp, [{"path": "a.mov", "duration": 5.0}])
|
|
edl, fcpxml = export.write_timeline(report["timeline_events"], out_dir="out")
|
|
```
|
|
|
|
## Completed assets and editor placement
|
|
|
|
`tasteforge.assets.ingest_assets(config_path, out_receipt)` records already
|
|
local image, video and GLB assets without uploading or generating them.
|
|
`validate_assets(receipt_path)` rechecks their bytes and lineage. Entries use
|
|
`id`, `modality`, `path` and `origin`: `local_passthrough`, `external_result`,
|
|
or `recovered_unverified`. An external result requires supplied provider
|
|
identifiers and a local evidence file. This verifies the supplied evidence,
|
|
not remote provider state. Optional `bundle_dir` binds each asset's
|
|
`request_id` to the validated multimodal plan; genre fields are derived from
|
|
that match instead of accepted as arbitrary claims.
|
|
|
|
`tasteforge.resolve.allocate_placements` validates local overlay assets and
|
|
allocates overlapping intervals above preserved video tracks.
|
|
`apply_placements` takes injected Resolve timeline and media-pool objects;
|
|
it does not connect to Resolve, save a project or render. Call it only after
|
|
selecting and verifying a distinct versioned target and saving a checkpoint:
|
|
|
|
```python
|
|
from tasteforge.resolve import apply_placements
|
|
|
|
receipt = apply_placements(
|
|
target_timeline, media_pool, events,
|
|
source_timeline="previous-cut", fps=30, base_track_count=16,
|
|
source_end_mode="exclusive", # verified host convention, never assumed
|
|
)
|
|
```
|
|
|
|
Each event supplies `id`, `asset`, `record_frame`, `frames`, `opacity` and an
|
|
explicit numeric `composite`. Optional `requires_alpha` checks decoded pixel
|
|
format. The adapter verifies immediate and final geometry, paths, properties,
|
|
track membership and base/audio preservation. A mismatch raises and may leave
|
|
partial edits in the new target; discard/restore that target instead of
|
|
retrying blindly. Its receipt proves in-memory placement only. Save and verify
|
|
the editor checkpoint separately before rendering or reporting delivery.
|
|
|
|
## Preserve an existing edit with an application bundle
|
|
|
|
From `skills/taste-application/scripts/`, compile a **local proposal**:
|
|
|
|
```bash
|
|
python3 workflow_graphs.py --kind apply-bundle --config /private/work/request.json --out /private/work/new-bundle.json
|
|
```
|
|
|
|
The request retains `source_video`, `brief`, and `style_steer`, and adds an
|
|
`integration` object. The existing `--kind apply` still produces exactly
|
|
`source_video` and `compiled_prompt`. The bundle embeds that unchanged payload;
|
|
it is not itself a fal request or an EDL/FCPXML input. No upload, download,
|
|
provider execution, media probing, grading, rendering or editor mutation occurs.
|
|
|
|
For preservation without any hosted source, use the same CLI with a request
|
|
containing **only** `{"local_only": true, "integration": {...}}`. This mode
|
|
does not compile or prepare a provider request: the bundle has
|
|
`local_only: true`, `provider_input: null`, `compiled_input_sha256: null`,
|
|
`provider_input_status: not_prepared_local_only` and
|
|
`insert_policy: none_preserve_baseline`. Candidates and inserts must be empty.
|
|
Do not supply `source_video`, `provider_input`, `compiled_prompt`, provider
|
|
brief/style fields, or a dummy URL. Local evidence and protected-stack checks
|
|
still run in full; this bundle cannot be submitted to fal.
|
|
|
|
The request's `local_only` flag must be an exact JSON boolean; omission defaults
|
|
to false. With false/omitted, the original provider-input compilation still
|
|
requires a real HTTPS source and its output shape remains unchanged. Existing
|
|
normal bundles therefore have no `local_only` field. Revalidation rejects
|
|
changed flags, mixed provider input or mode fields, and added candidates/inserts.
|
|
At the API level use `build_application_bundle(integration, None, local_only=True)`;
|
|
normal calls retain their existing two positional arguments.
|
|
|
|
`integration` requires:
|
|
|
|
| Field | Contract |
|
|
|---|---|
|
|
| `baseline` | `project_file`, `snapshot_file`, `project_name`, `timeline_name`, `fps`, `timeline_range` |
|
|
| `source` | `media`, `track`, `clip_index`, `media_frames`, `fps`, `source_range`, `timeline_range` |
|
|
| `audio` | One source-shaped binding for **every** original audio clip, retaining its complete placement and trim |
|
|
| `protected_intervals` | Nonempty list of `{range: [start, end], reason: text}` |
|
|
|
|
File records are `{path, bytes, sha256}`: canonical absolute path, positive
|
|
integer byte count, lowercase SHA-256 of resident regular bytes. Paths and
|
|
parents must not be symlinks. Requests and JSON evidence are limited to 8 MiB;
|
|
duplicate JSON keys and nonfinite values fail. Missing/offloaded files fail
|
|
without hydration. Use a private output directory outside any public repository;
|
|
bundles contain local paths, prompts and hosted media URLs. Existing output
|
|
files are never overwritten.
|
|
|
|
FPS is a reduced `{numerator, denominator}` pair of positive exact integers.
|
|
Every range is half-open in integer frames; booleans and decimal frame counts
|
|
are invalid. Audio source offsets/capacity are expressed at the timeline FPS,
|
|
not in audio samples. Bindings must match the snapshot path, track/index and
|
|
trim geometry exactly; source subsets must retain the same time mapping.
|
|
|
|
The native snapshot contains `project`, `timeline`,
|
|
`settings.timelineFrameRate`, and `timeline_readback: {video1: [...], audio1: [...]}`.
|
|
Each clip supplies `name`, `path`, `start`, `end`, `left_offset`, `right_offset`,
|
|
`enabled` and `properties`. Preserve all original entries, including disabled
|
|
clips; native generators may have `path: null` but cannot serve as a file-bound
|
|
source/audio reference. Native FPS must agree; Resolve labels `23.976`, `29.97`
|
|
and `59.94` map explicitly to the corresponding `/1001` rates. Other native
|
|
FPS labels must be short decimal/rational forms, never exponent notation.
|
|
Capture each timeline while active; do not rewrite state using inactive reads.
|
|
|
|
Optional `candidates`, `inserts` and `historical_receipts` default to empty.
|
|
The result uses `preserve_native_timeline`, derives the full protected stack,
|
|
and proposes zero inserts by default. Pending/rejected candidates cannot be
|
|
inserted. A resolved candidate requires local `media`, `media_frames`, `fps`,
|
|
unique `id`, `origin: provider_generated`, `relationship: generated_variation`,
|
|
`review_status`, source/input hashes, and a hash-bound `generation_receipt`.
|
|
That receipt names `request_id`, `source_url`, `source_sha256`,
|
|
`candidate_sha256` and `compiled_input_sha256`. Unresolved historical URLs may
|
|
remain in separate local historical receipts; they never become candidates.
|
|
|
|
An insert supplies `candidate_id`, `candidate_range`, `timeline_range`,
|
|
`retime: none` and an `approval_file`. The approval must state `status: approved`
|
|
and match candidate/source/input hashes, both ranges, and `edit_context_sha256`
|
|
from a zero-insert bundle. This context hashes the complete baseline, source,
|
|
audio and protection configuration, preventing approval reuse on another edit
|
|
or timebase. Only after actual review should that approval evidence be supplied.
|
|
The insert policy permits a new video track and preserves baseline audio;
|
|
overlaps with protected intervals or other inserts, mismatched FPS, and retiming
|
|
are rejected. Original clips are never removed or rewritten by this module.
|
|
|
|
API: `tasteforge.integration.build_application_bundle(integration, compiled_input)`
|
|
returns an independent object; `validate_application_bundle(bundle)` rereads and
|
|
checks its evidence. Revalidate immediately before any separately implemented
|
|
editor operation. These checks prove local bytes and supplied metadata only:
|
|
they do not authenticate a reviewer, prove remote upload identity, probe actual
|
|
media timing, prove the snapshot was honestly captured, or establish visual
|
|
approval. Source/candidate timing must already have been independently measured.
|
|
The synthetic cases in `tests/test_integration.py` are executable format examples.
|
|
|
|
## Tests, lint, types
|
|
|
|
```bash
|
|
python3 -m unittest discover -s skills/taste-application/tests -v # full suite (offline, deterministic)
|
|
ruff check skills/taste-application/scripts/tasteforge # lint (pip install ruff)
|
|
mypy skills/taste-application/scripts/tasteforge # types (pip install mypy)
|
|
python3 -m compileall -q skills/taste-application/scripts/tasteforge # syntax check
|
|
```
|
|
|
|
## Boundaries
|
|
|
|
- No network calls, no credentials, no provider account access — ever.
|
|
- A local Fal reference never means a provider workflow was saved; see
|
|
`provenance.provider_reference()`.
|
|
- Raw recovered sources (videos, LUTs, stills, meshes) stay out of Git; the
|
|
fixture is metadata-only and documented in the skill's `SOURCE.md`.
|
|
|
|
## Reusable media adapters
|
|
|
|
Install optional dependencies with `python3 -m pip install './skills/taste-application/scripts[media]'`,
|
|
`[capcut]`, or `[manim]` as needed. FFmpeg/ffprobe are external executables.
|
|
|
|
- `python3 -m tasteforge.media.stills INPUT OUTPUT --duration 4 --fps 30` animates a still with configurable canvas and normalized crop endpoints through the Python API.
|
|
- `python3 -m tasteforge.media.glitch --help` exposes seeded local drift, feedback, mosh and pixel-sort effects. Pixel-sort randomness is disabled for repeatability.
|
|
- `python3 -m tasteforge.media.capcut --help` creates a new named draft from local clips. Existing drafts cannot be overwritten; choose a fresh name. Native editor readback and save verification remain separate application checks.
|
|
- `tasteforge.media.manim_geo` supplies reusable geometry scenes. Use Manim's render options for frame rate, canvas and output; project subclasses may set the seed and labels.
|
|
|
|
Still and glitch outputs reject collisions unless `--overwrite` is explicit;
|
|
rendering uses a temporary output so a failed process preserves the existing
|
|
file. Crop rectangles are fitted to the output aspect ratio using both width
|
|
and height. The Ito example wrappers retain its scene choices and file names.
|