Merge pull request 'feat(v2v): real multi-strength Right variants for the forest scale (vertical slice)' (#11) from feature/v2v-forest-right-variants into main

This commit was merged in pull request #11.
This commit is contained in:
2026-06-08 13:58:53 +00:00
7 changed files with 499 additions and 45 deletions
+10 -1
View File
@@ -175,7 +175,16 @@ every transition, so fast navigation stays responsive on placeholder media.
scales-library/right-axis design §1/§4); a real multi-strength flow-stabilized
re-bake per base clip + the multilingual label/string tables + TTS. The
**scale-ring transitions** (forward/reverse per-edge morphs) are part of this
offline pipeline too — the simulator currently uses placeholders.
offline pipeline too.
- **Vertical slice done (session 0012):** the Right-variant half is
productionized and run for the **forest** scale — `simulator/bake_right_variants.py`
(the POC flow-restyle algorithm, repo-resident) bakes real strengths 14 on a
by-eye keyframe-strength ramp; forest now carries real Right variants in the
sim, so the dreamlike-axis look is judgeable on real footage. **Still
deferred:** real Right variants for cosmos/abyss (need their real strict-PD
bases sourced first) and the **real i2v ring transitions** (need a 2nd real
base + a generative-video model not in the POC). Plan:
[`2026-06-08-v2v-vertical-slice-forest-right-variants.md`](./superpowers/plans/2026-06-08-v2v-vertical-slice-forest-right-variants.md).
- **Catalog model changes** — audio *source* + "neutral base" vs "altered
variant" flag (sub-project 2 territory, design §13).
+15 -9
View File
@@ -298,16 +298,22 @@ piece moved from *selecting* clips to *altering* them.)
**One-time setup — populate the sample footage** (look-tuning only; not shipped
content):
python simulator/setup_sample_media.py # the forest scale (real POC base + Right variants)
python simulator/setup_scales_media.py # the cosmos + abyss scales + ring transitions
python simulator/setup_sample_media.py # stage the forest neutral base
python -m simulator.bake_right_variants --scale forest # real Right variants (SD on MPS, ~11 min)
python simulator/setup_scales_media.py # the cosmos + abyss scales + ring transitions
`setup_sample_media.py` copies the session-0008 POC artifacts (`~/hef-poc/out/`)
into `simulator/sample_media/forest/` — the neutral base clip and the real
flow-stabilized Right restyle — and generates placeholder intermediate Right
strengths. `setup_scales_media.py` makes the scale **ring** demonstrable: cheap
synthetic placeholder bases for the two true-PD scales (`cosmos` = NASA/Hubble,
`abyss` = NOAA Ocean Exploration) plus the per-edge zoom/warp transition clips.
The `.mp4` binaries are gitignored.
`setup_sample_media.py` stages the forest neutral base from the session-0008 POC
artifacts (`~/hef-poc/out/neutral.mp4`). `bake_right_variants.py` then produces
the **real** flow-stabilized painterly Right variants (strengths 14) for the
forest scale — SD img2img keyframes + Farneback optical-flow tweens on Apple MPS,
the temporally-calm POC algorithm, productionized (scales design §1/§4). The
keyframe-strength ramp per Right level is by-eye tunable in
`simulator/bake_right_variants.py`. `setup_scales_media.py` makes the scale
**ring** demonstrable: cheap synthetic placeholder bases for the two true-PD
scales (`cosmos` = NASA/Hubble, `abyss` = NOAA Ocean Exploration) plus the
per-edge zoom/warp transition clips — these scales carry a raw base only (no real
Right variants yet; the baker can extend to them once their real footage is
sourced). The `.mp4` binaries are gitignored.
**Run it (Docker):**
@@ -0,0 +1,239 @@
# v2v Vertical Slice — Real Forest Right Variants Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Produce the first *real* (not placeholder) multi-strength Right-axis
restyle media for the simulator — flow-stabilized painterly variants of the
forest scale at Right strengths 14 — so the dreamlike-axis *look* can finally be
judged by eye on real footage, breaking the "can't judge the ring liked without
real content" chicken-and-egg (operator chose a vertical slice, session 0012).
**Architecture:** Productionize the proven session-0008 POC restyle algorithm
(`~/hef-poc/flow_restyle.py`) into a repo-resident offline baker,
`simulator/bake_right_variants.py`, a sibling of the existing
`setup_sample_media.py` / `setup_scales_media.py` media-generation scripts. The
algorithm is unchanged (extract frames → SD img2img on keyframes → Farneback
optical-flow warp + light refine on tweens → reassemble), which sessions 0008
proved is temporally calm (no boiling). The one new idea is a **strength curve**:
Right level 14 maps to an increasing keyframe img2img `strength`, so the four
discrete pre-baked variants form a subtle→strong dreamlike ramp. Only the curve
helper is unit-tested; the rendered media is verified by eye (screenshots), as
the other media-setup scripts are.
**Tech Stack:** Python, diffusers `AutoPipelineForImage2Image` + `stabilityai/sd-turbo`
(cached, 4.8 G) on Apple MPS (torch 2.12, available in the project `.venv`),
OpenCV Farneback optical flow, imageio-ffmpeg. ~2.7 min/clip × 4 ≈ ~11 min local
compute.
**Scope note — what this slice is NOT:** the *real i2v ring transition* half of
the v2v build is deferred: it needs a second real scale base (cosmos/abyss are
still synthetic placeholders) plus a generative-video model not in the POC, and
real strict-PD footage sourcing (NASA/NOAA, sub-project 2). This slice does the
Right-variant half, which is fully achievable now (forest has a real base).
---
### Task 1: The Right strength curve (pure, testable)
**Files:**
- Create: `simulator/bake_right_variants.py`
- Test: `tests/test_bake_right_variants.py`
- [ ] **Step 1: Write the failing test**
```python
# tests/test_bake_right_variants.py
import pytest
from simulator.bake_right_variants import right_strength_params, RIGHT_LEVELS
def test_levels_are_one_through_four():
assert RIGHT_LEVELS == (1, 2, 3, 4)
def test_strength_increases_with_level():
keys = [right_strength_params(l)[0] for l in RIGHT_LEVELS]
assert keys == sorted(keys) # monotonic ramp
assert keys[0] < keys[-1] # subtle -> strong
def test_tween_strength_is_a_fraction_of_key_strength():
for l in RIGHT_LEVELS:
key, tween = right_strength_params(l)
assert 0.0 < tween < key # tween is a light refine only
def test_rejects_out_of_range_level():
with pytest.raises(ValueError):
right_strength_params(0)
with pytest.raises(ValueError):
right_strength_params(5)
```
- [ ] **Step 2: Run test to verify it fails**
Run: `.venv/bin/python -m pytest tests/test_bake_right_variants.py -q`
Expected: FAIL (ModuleNotFoundError / ImportError — module not created yet)
- [ ] **Step 3: Write minimal implementation (curve only, no torch import at module load)**
```python
# simulator/bake_right_variants.py (top of file)
"""Offline baker: real flow-stabilized painterly Right-axis variants (scales
design §1/§4). Productionizes the session-0008 POC (~/hef-poc/flow_restyle.py):
SD img2img keyframes + Farneback optical-flow tweens = temporally calm restyle.
Right level -> keyframe img2img strength ramp (subtle -> strong); tween strength
is a light refine fraction of the keyframe strength. Curve is by-eye tunable.
Usage: .venv/bin/python -m simulator.bake_right_variants [--scale forest]
Requires: torch+MPS, diffusers, sd-turbo (cached), opencv, imageio-ffmpeg.
"""
from __future__ import annotations
RIGHT_LEVELS = (1, 2, 3, 4)
# Keyframe img2img strength per Right level (by-eye ramp). Level 4 ~ the POC's
# proven 0.5-0.58 painterly look; level 1 is a barely-there dream haze.
_KEY_STRENGTH = {1: 0.28, 2: 0.38, 3: 0.48, 4: 0.58}
_TWEEN_RATIO = 0.36 # POC ratio (0.18 / 0.5): tweens are a light refine only
def right_strength_params(level: int) -> tuple[float, float]:
"""(keyframe_strength, tween_strength) for a Right level in RIGHT_LEVELS."""
if level not in _KEY_STRENGTH:
raise ValueError(f"Right level must be one of {RIGHT_LEVELS}, got {level}")
key = _KEY_STRENGTH[level]
return key, round(key * _TWEEN_RATIO, 4)
```
- [ ] **Step 4: Run test to verify it passes**
Run: `.venv/bin/python -m pytest tests/test_bake_right_variants.py -q`
Expected: PASS (4 passed)
- [ ] **Step 5: Commit**
```bash
git add simulator/bake_right_variants.py tests/test_bake_right_variants.py
git commit -m "feat(pipeline): Right strength curve for the v2v variant baker"
```
---
### Task 2: The baker (flow restyle, productionized)
**Files:**
- Modify: `simulator/bake_right_variants.py`
No unit test for the render itself (non-trivial ML output; verified by eye in
Task 4). The pure curve is already covered by Task 1.
- [ ] **Step 1: Add the restyle engine + per-level bake, porting the POC algorithm verbatim**
Port `extract`, `warp`, and the keyframe/tween loop from
`~/hef-poc/flow_restyle.py` unchanged (it is the approved calm algorithm). Wrap
it as `restyle_clip(src, out, key_strength, tween_strength, *, fps=12.0, keyint=24)`
and add a `bake_scale(scale="forest")` that loops `RIGHT_LEVELS`, calling
`restyle_clip` with `right_strength_params(level)` and writing
`simulator/sample_media/<scale>/right<level>.mp4`. Load the SD pipeline ONCE and
reuse it across levels. `PROMPT`/`NEG`/Farneback params/seed = the POC's.
Source base = `simulator/sample_media/<scale>/base.mp4` (forest = the real
Yosemite POC clip). `main()` parses `--scale` (default `forest`), `--fps`,
`--keyint` and calls `bake_scale`.
- [ ] **Step 2: Smoke-check the module imports and the curve still passes**
Run: `.venv/bin/python -m pytest tests/test_bake_right_variants.py -q`
Expected: PASS (torch imported lazily inside the bake funcs, not at module load,
so the test stays fast and import-safe)
- [ ] **Step 3: Commit**
```bash
git add simulator/bake_right_variants.py
git commit -m "feat(pipeline): forest Right-variant baker (POC flow restyle, productionized)"
```
---
### Task 3: Run the bake (real media, ~11 min)
**Files:**
- Writes (gitignored): `simulator/sample_media/forest/right{1,2,3,4}.mp4`
- [ ] **Step 1: Run the baker**
Run: `.venv/bin/python -m simulator.bake_right_variants --scale forest`
Expected: 4 clips written; per-level timing printed; ~11 min total. Replaces the
3 placeholder strengths (13) and the single real strength (4) with a consistent
real ramp.
- [ ] **Step 2: Confirm the outputs exist and are real video**
Run: `ls -la simulator/sample_media/forest/right*.mp4`
Expected: right14.mp4 present, each a multi-MB H.264 clip.
(No commit — media is gitignored; it is reproduced by the baker.)
---
### Task 4: Wire the manifest + verify by eye
**Files:**
- Modify: `simulator/sample_media/manifest.json` (forest `right_variants` models)
- Modify: `docs/USER_GUIDE.md` (note real forest Right variants + the baker)
- Modify: `docs/ROADMAP.md` (v2v slice progress)
- [ ] **Step 1: Update the forest variant models in the manifest**
Set every forest `right_variants` entry (14) `model` to
`"sd-turbo+farneback-flow"` (drop the `"placeholder"` labels) — they are all real
bakes now.
- [ ] **Step 2: Boot the sim and drive the Right axis across 0→4 on forest**
Run: `.venv/bin/python -m uvicorn simulator.app:app --port 8013` (background),
navigate to the forest scale, and capture headless screenshots at Right 0/1/2/3/4.
Expected: a coherent subtle→strong painterly ramp on the real Yosemite footage,
temporally calm (no boiling), grade/overlay still compose on top.
- [ ] **Step 3: Update docs**
USER_GUIDE: note forest now carries real flow-stabilized Right variants generated
by `simulator/bake_right_variants.py`. ROADMAP: mark the v2v Right-variant vertical
slice done; note the i2v ring-transition half still deferred (needs a 2nd real
base + video model).
- [ ] **Step 4: Run the full suite**
Run: `.venv/bin/python -m pytest -q`
Expected: PASS (existing + Task-1 curve tests).
- [ ] **Step 5: Commit**
```bash
git add simulator/sample_media/manifest.json docs/USER_GUIDE.md docs/ROADMAP.md
git commit -m "feat(sim): forest carries real multi-strength Right variants; docs"
```
---
## Self-Review
**Spec coverage:** scales design §1 (local restyle pipeline) + §4 (economics:
local bake) → Tasks 23 productionize + run it. The operator's vertical-slice
choice (real Right variants for one scale to judge the look) → Tasks 34. The
deferred i2v transition half is explicitly scoped out (noted in header + Task 4).
The strength curve (the one new design element) → Task 1.
**Placeholder scan:** none — the baker algorithm is a verbatim port of the
existing `~/hef-poc/flow_restyle.py` (cited), the curve is concrete constants,
commands are exact.
**Type consistency:** `right_strength_params(level) -> (key, tween)` defined in
Task 1, consumed by `bake_scale` in Task 2. `RIGHT_LEVELS` shared. Output paths
`simulator/sample_media/forest/right<level>.mp4` consistent with the manifest's
existing forest `right_variants` map (Task 4).
+193
View File
@@ -0,0 +1,193 @@
"""Offline baker: real flow-stabilized painterly Right-axis variants (scales
design §1/§4).
Productionizes the session-0008 POC (``~/hef-poc/flow_restyle.py``): SD img2img
on KEYFRAMES + Farneback optical-flow warp on in-between frames = a temporally
calm painterly restyle (no per-frame "boiling" — the look the operator approved
in 0008). The one new idea on top of the POC is a **strength curve**: each Right
level 1-4 maps to an increasing keyframe img2img ``strength`` so the four
discrete pre-baked variants form a subtle -> strong dreamlike ramp. Level 0 is
always the raw base (handled by the runtime, not baked here).
The curve (``right_strength_params``) is pure + unit-tested; the rendered media
is verified by eye (like the other ``simulator/setup_*_media.py`` scripts). torch
/ diffusers are imported lazily inside the bake functions so importing this
module (e.g. for the curve test) is fast and dependency-light.
Usage:
.venv/bin/python -m simulator.bake_right_variants [--scale forest]
[--fps 12] [--keyint 24]
Requires: torch + Apple MPS, diffusers, ``stabilityai/sd-turbo`` (cached),
opencv-python, imageio-ffmpeg. ~2.7 min/clip x 4 levels ~= ~11 min local compute.
"""
from __future__ import annotations
import argparse
import glob
import os
import subprocess
import tempfile
import time
from pathlib import Path
MEDIA = Path(__file__).parent / "sample_media"
RIGHT_LEVELS = (1, 2, 3, 4)
# Keyframe img2img strength per Right level (by-eye ramp). sd-turbo's painterly
# transformation is sharply NON-LINEAR in strength — it barely registers below
# ~0.5 and ramps hard through ~0.5-0.65 (verified by eye, session 0012: a linear
# 0.28-0.58 ramp left levels 1-3 ~indistinguishable from raw). So the curve is
# clustered in that active band for real perceptual progression: a gentle dream
# haze (L1) -> clearly painterly (L3, ~the POC's proven look) -> strongest (L4).
# Still by-eye tunable — re-run the baker after editing.
_KEY_STRENGTH = {1: 0.46, 2: 0.52, 3: 0.58, 4: 0.64}
_TWEEN_RATIO = 0.36 # POC ratio (0.18 / 0.5): tweens are a light refine only.
# The restyle target (matches the POC spike that the operator approved).
PROMPT = (
"impressionist oil painting, dreamlike, soft painterly brushstrokes, "
"ethereal, luminous, fine art"
)
NEG = "text, watermark, frame, border, ugly, deformed, lowres"
SEED = 42
def right_strength_params(level: int) -> tuple[float, float]:
"""(keyframe_strength, tween_strength) for a Right level in RIGHT_LEVELS."""
if level not in _KEY_STRENGTH:
raise ValueError(f"Right level must be one of {RIGHT_LEVELS}, got {level}")
key = _KEY_STRENGTH[level]
return key, round(key * _TWEEN_RATIO, 4)
# --- the restyle engine (verbatim port of ~/hef-poc/flow_restyle.py) ---
def _ffmpeg() -> str:
import imageio_ffmpeg
return imageio_ffmpeg.get_ffmpeg_exe()
def _run(cmd: list[str]) -> None:
subprocess.run(cmd, check=True, capture_output=True)
def _extract(ff: str, src: str, d: str, fps: float) -> list[str]:
_run([ff, "-hide_banner", "-v", "error", "-i", src, "-vf", f"fps={fps}",
os.path.join(d, "f_%05d.png"), "-y"])
return sorted(glob.glob(os.path.join(d, "f_*.png")))
def _warp(prev_bgr, src_prev_gray, src_cur_gray):
"""Warp the previous stylized frame to align with the current source frame
via Farneback optical flow — this is what kills the per-frame flicker."""
import cv2
import numpy as np
flow = cv2.calcOpticalFlowFarneback(
src_prev_gray, src_cur_gray, None,
pyr_scale=0.5, levels=3, winsize=21, iterations=3,
poly_n=7, poly_sigma=1.5, flags=0)
h, w = src_cur_gray.shape
gx, gy = np.meshgrid(np.arange(w), np.arange(h))
mx = (gx + flow[..., 0]).astype(np.float32)
my = (gy + flow[..., 1]).astype(np.float32)
return cv2.remap(prev_bgr, mx, my, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT)
def _load_pipe(model: str):
"""Load the SD img2img pipeline once (reused across all levels)."""
import torch
from diffusers import AutoPipelineForImage2Image
dev = "mps" if torch.backends.mps.is_available() else "cpu"
pipe = AutoPipelineForImage2Image.from_pretrained(
model, torch_dtype=torch.float16 if dev == "mps" else torch.float32,
safety_checker=None).to(dev)
return pipe, dev
def restyle_clip(pipe, dev, src: str, out: str, key_strength: float,
tween_strength: float, *, fps: float = 12.0, keyint: int = 24) -> None:
"""Restyle one clip: SD img2img keyframes + flow-warped light-refine tweens."""
import cv2
import numpy as np
import torch
from PIL import Image
ff = _ffmpeg()
gen = torch.Generator(device=dev).manual_seed(SEED)
def img2img(pil, strength):
steps = max(2, int(np.ceil(1.0 / max(strength, 0.05))) + 1)
return pipe(prompt=PROMPT, negative_prompt=NEG, image=pil,
strength=strength, guidance_scale=0.0,
num_inference_steps=steps, generator=gen).images[0]
with tempfile.TemporaryDirectory() as d:
frames = _extract(ff, src, d, fps)
print(f" [frames] {len(frames)} key_s={key_strength} tween_s={tween_strength}")
prev_styled = None # BGR uint8
prev_src_gray = None
t0 = time.time()
for i, fp in enumerate(frames):
src_bgr = cv2.imread(fp)
h0, w0 = src_bgr.shape[:2]
scale = 768 / max(h0, w0)
wh = (int(w0 * scale) // 8 * 8, int(h0 * scale) // 8 * 8)
src_small = cv2.resize(src_bgr, wh)
src_gray = cv2.cvtColor(src_small, cv2.COLOR_BGR2GRAY)
if i % keyint == 0 or prev_styled is None:
pil = Image.fromarray(cv2.cvtColor(src_small, cv2.COLOR_BGR2RGB))
out_img = img2img(pil, key_strength)
else:
warped = _warp(prev_styled, prev_src_gray, src_gray)
pil = Image.fromarray(cv2.cvtColor(warped, cv2.COLOR_BGR2RGB))
out_img = img2img(pil, tween_strength) # light refine only
styled = cv2.cvtColor(np.array(out_img), cv2.COLOR_RGB2BGR)
prev_styled, prev_src_gray = styled, src_gray
cv2.imwrite(os.path.join(d, f"s_{i+1:05d}.png"),
cv2.resize(styled, (w0, h0)))
if i % 12 == 0:
print(f" frame {i+1}/{len(frames)} {time.time()-t0:.1f}s")
_run([ff, "-hide_banner", "-v", "error", "-framerate", str(fps),
"-i", os.path.join(d, "s_%05d.png"), "-c:v", "libx264", "-crf", "20",
"-preset", "medium", "-pix_fmt", "yuv420p", out, "-y"])
print(f" [done] {time.time()-t0:.1f}s -> {out}")
def bake_scale(scale: str = "forest", *, fps: float = 12.0, keyint: int = 24,
model: str = "stabilityai/sd-turbo") -> None:
"""Bake all RIGHT_LEVELS variants for one scale's base clip."""
base = MEDIA / scale / "base.mp4"
if not base.exists():
raise FileNotFoundError(
f"no base clip at {base} — run the media-setup scripts first")
print(f"[bake] scale={scale} base={base}")
pipe, dev = _load_pipe(model)
print(f"[device] {dev}")
for level in RIGHT_LEVELS:
key_s, tween_s = right_strength_params(level)
out = MEDIA / scale / f"right{level}.mp4"
print(f" [right {level}] -> {out}")
restyle_clip(pipe, dev, str(base), str(out), key_s, tween_s,
fps=fps, keyint=keyint)
print(f"[bake] {scale} done — {len(RIGHT_LEVELS)} real Right variants")
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--scale", default="forest")
ap.add_argument("--fps", type=float, default=12.0)
ap.add_argument("--keyint", type=int, default=24)
a = ap.parse_args()
bake_scale(a.scale, fps=a.fps, keyint=a.keyint)
if __name__ == "__main__":
main()
+3 -3
View File
@@ -25,9 +25,9 @@
"license": "poc-sample (look-tuning only; not shipped content)",
"source": "hef-poc/out/neutral.mp4",
"right_variants": {
"1": {"file": "forest/right1.mp4", "model": "placeholder"},
"2": {"file": "forest/right2.mp4", "model": "placeholder"},
"3": {"file": "forest/right3.mp4", "model": "placeholder"},
"1": {"file": "forest/right1.mp4", "model": "sd-turbo+farneback-flow"},
"2": {"file": "forest/right2.mp4", "model": "sd-turbo+farneback-flow"},
"3": {"file": "forest/right3.mp4", "model": "sd-turbo+farneback-flow"},
"4": {"file": "forest/right4.mp4", "model": "sd-turbo+farneback-flow"}
},
"annotations": [
+13 -32
View File
@@ -1,52 +1,33 @@
"""Populate simulator/sample_media/forest/ from the session-0008 POC artifacts.
"""Stage the forest scale's neutral BASE clip from the session-0008 POC artifacts.
Copies the real neutral base + the real flow-stabilized Right restyle out of
~/hef-poc/out/, and generates placeholder intermediate Right strengths (1..3) by
blending the base toward the real restyle with ffmpeg. The media binaries are
gitignored; only the manifest is committed. Sample footage is for look-tuning
only, not shipped content.
Copies the real neutral Yosemite base out of ~/hef-poc/out/ into
simulator/sample_media/forest/. The real multi-strength Right variants are then
produced by the offline baker (`simulator/bake_right_variants.py`) — this script
no longer generates placeholder Right strengths, so re-running it never clobbers
a real bake. The media binaries are gitignored; only the manifest is committed.
Sample footage is for look-tuning only, not shipped content.
Usage: python simulator/setup_sample_media.py
Requires: ffmpeg on PATH (or `pip install imageio-ffmpeg`), and ~/hef-poc/out/.
Usage:
python simulator/setup_sample_media.py # stage the base
python -m simulator.bake_right_variants --scale forest # then bake variants
Requires: ~/hef-poc/out/neutral.mp4 (the POC neutral base).
"""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
POC = Path.home() / "hef-poc" / "out"
DEST = Path(__file__).parent / "sample_media" / "forest"
def _ffmpeg() -> str:
if shutil.which("ffmpeg"):
return "ffmpeg"
import imageio_ffmpeg
return imageio_ffmpeg.get_ffmpeg_exe()
def main() -> None:
DEST.mkdir(parents=True, exist_ok=True)
base = DEST / "base.mp4"
right4 = DEST / "right4.mp4"
shutil.copyfile(POC / "neutral.mp4", base)
shutil.copyfile(POC / "right_flow.mp4", right4)
ff = _ffmpeg()
# Placeholder strengths 1..3: opacity-blend base toward the real restyle.
for strength, alpha in ((1, 0.25), (2, 0.5), (3, 0.75)):
out = DEST / f"right{strength}.mp4"
subprocess.run(
[ff, "-y", "-i", str(base), "-i", str(right4),
"-filter_complex",
f"[1:v]format=yuva444p,colorchannelmixer=aa={alpha}[top];"
f"[0:v][top]overlay=shortest=1[v]",
"-map", "[v]", "-an", str(out)],
check=True,
)
print(f"generated {out.name} (alpha {alpha})")
print(f"sample media ready in {DEST}")
print(f"staged {base} (real POC neutral base)")
print("next: python -m simulator.bake_right_variants --scale forest")
if __name__ == "__main__":
+26
View File
@@ -0,0 +1,26 @@
import pytest
from simulator.bake_right_variants import RIGHT_LEVELS, right_strength_params
def test_levels_are_one_through_four():
assert RIGHT_LEVELS == (1, 2, 3, 4)
def test_strength_increases_with_level():
keys = [right_strength_params(l)[0] for l in RIGHT_LEVELS]
assert keys == sorted(keys) # monotonic ramp
assert keys[0] < keys[-1] # subtle -> strong
def test_tween_strength_is_a_fraction_of_key_strength():
for l in RIGHT_LEVELS:
key, tween = right_strength_params(l)
assert 0.0 < tween < key # tween is a light refine only
def test_rejects_out_of_range_level():
with pytest.raises(ValueError):
right_strength_params(0)
with pytest.raises(ValueError):
right_strength_params(5)