Files
ECC/skills/taste-application/scripts/tasteforge/README.md
T
928c1dea72 feat(tasteforge): package reusable workflows and preserve native edits (#3033)
* 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>
2026-09-10 15:31:36 +01:00

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.