mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-18 15:50:25 +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>
90 lines
3.5 KiB
Python
90 lines
3.5 KiB
Python
"""Original Blender workflow boundary tests without importing bpy."""
|
|
|
|
import importlib.util
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
SCRIPT = (
|
|
Path(__file__).resolve().parents[1]
|
|
/ "skills/taste-application/scripts/blender_prop.py"
|
|
)
|
|
spec = importlib.util.spec_from_file_location("blender_prop", SCRIPT)
|
|
prop = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(prop)
|
|
|
|
|
|
class BlenderPropTests(unittest.TestCase):
|
|
def test_frame_geometry_validation(self):
|
|
for frames, width, height, fps in [
|
|
(0, 640, 480, 30),
|
|
(48, 0, 480, 30),
|
|
(48, 640, 480, float("nan")),
|
|
(True, 640, 480, 30),
|
|
(48, 640, 480, 0),
|
|
]:
|
|
with self.subTest(frames=frames, fps=fps), self.assertRaises(ValueError):
|
|
prop.validate_settings(frames, width, height, fps)
|
|
prop.validate_settings(48, 1920, 1080, 29.97)
|
|
|
|
def test_legacy_and_layered_fcurves(self):
|
|
curve = SimpleNamespace(
|
|
keyframe_points=[SimpleNamespace(interpolation="BEZIER")]
|
|
)
|
|
old = SimpleNamespace(fcurves=[curve])
|
|
prop.linearize_action(old)
|
|
self.assertEqual(curve.keyframe_points[0].interpolation, "LINEAR")
|
|
curve.keyframe_points[0].interpolation = "BEZIER"
|
|
bag = SimpleNamespace(fcurves=[curve])
|
|
strip = SimpleNamespace(channelbags=[bag])
|
|
new = SimpleNamespace(layers=[SimpleNamespace(strips=[strip])])
|
|
prop.linearize_action(new)
|
|
self.assertEqual(curve.keyframe_points[0].interpolation, "LINEAR")
|
|
|
|
def test_output_cannot_overwrite_or_follow_symlinks(self):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
root = Path(temporary).resolve()
|
|
target = root / "scene.blend"
|
|
prop.validate_output(target)
|
|
target.touch()
|
|
with self.assertRaises(ValueError):
|
|
prop.validate_output(target)
|
|
link = root / "link.blend"
|
|
link.symlink_to(target)
|
|
with self.assertRaises(ValueError):
|
|
prop.validate_output(link)
|
|
|
|
def test_geometry_rejects_nonfinite_and_empty(self):
|
|
for lower, upper in [((0, 0, 0), (0, 0, 0)), ((0, 0, 0), (float("inf"), 1, 1))]:
|
|
with self.assertRaises(ValueError):
|
|
prop.validate_bounds(lower, upper)
|
|
prop.validate_bounds((-1, -1, -1), (1, 1, 1))
|
|
|
|
def test_landscape_camera_preserves_sphere_fit(self):
|
|
self.assertAlmostEqual(
|
|
prop.camera_distance(1, 1024, 1024), (3.2**2 + 0.8**2) ** 0.5
|
|
)
|
|
self.assertGreater(
|
|
prop.camera_distance(1, 1920, 1080), prop.camera_distance(1, 1024, 1024)
|
|
)
|
|
|
|
def test_render_receipt_requires_every_frame_and_finished_status(self):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
root = Path(temporary)
|
|
with self.assertRaises(RuntimeError):
|
|
prop.verify_render({"FINISHED"}, root, 2)
|
|
for frame in (1, 2):
|
|
(root / f"turn_{frame:04d}.png").write_bytes(b"png")
|
|
with self.assertRaises(RuntimeError):
|
|
prop.verify_render({"CANCELLED"}, root, 2)
|
|
prop.verify_render({"FINISHED"}, root, 2)
|
|
(root / "turn_0002.png").write_bytes(b"")
|
|
with self.assertRaises(RuntimeError):
|
|
prop.verify_render({"FINISHED"}, root, 2)
|
|
|
|
def test_lab_neutral_white(self):
|
|
self.assertTrue(
|
|
all(0.99 <= value <= 1 for value in prop._lab_to_linear_srgb(100, 0, 0))
|
|
)
|