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>
115 lines
4.5 KiB
Python
115 lines
4.5 KiB
Python
"""Failing-first tests for the timeline timebase and EDL/FCPXML export.
|
|
|
|
Numeric expectations are canonicalized from the recovered gen4
|
|
``taste/timeline.py`` self-checks and the recovered ``flashethereal-cut.edl``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import xml.etree.ElementTree as ET
|
|
from fractions import Fraction
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1] / "scripts"
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
from tasteforge import export, timeline # noqa: E402
|
|
|
|
CLIPS = [
|
|
{"path": "/tmp/media/a.mov", "duration": 1.5, "name": "alpha"},
|
|
{"path": "/tmp/media/b.mov", "duration": 2.25, "name": "beta"},
|
|
{"path": "/tmp/media/c.mov", "duration": 0.75, "name": "gamma"},
|
|
]
|
|
|
|
|
|
class TimebaseTests(unittest.TestCase):
|
|
def test_ntsc_snap(self):
|
|
self.assertEqual(timeline.fps_fraction(29.97), Fraction(30000, 1001))
|
|
self.assertEqual(timeline.fps_fraction(23.976), Fraction(24000, 1001))
|
|
self.assertEqual(timeline.fps_fraction(24), Fraction(24, 1))
|
|
|
|
def test_negative_fps_rejected(self):
|
|
with self.assertRaises(ValueError):
|
|
timeline.fps_fraction(-3)
|
|
|
|
def test_seconds_to_frames_rounds_half_away_from_zero(self):
|
|
self.assertEqual(timeline.seconds_to_frames(0.5, 24), 12)
|
|
self.assertEqual(timeline.seconds_to_frames(1.25, 24), 30)
|
|
|
|
def test_rational_time_strings(self):
|
|
self.assertEqual(timeline.frames_to_rational(1, 29.97), "1001/30000s")
|
|
self.assertEqual(timeline.frames_to_rational(30, 29.97), "1001/1000s")
|
|
self.assertEqual(timeline.frames_to_rational(120, 24), "5s")
|
|
self.assertEqual(timeline.seconds_to_rational(2.5, 24), "5/2s")
|
|
|
|
def test_timecode_drop_frame(self):
|
|
self.assertEqual(timeline.frames_to_timecode(1800, 29.97), "00:01:00:02")
|
|
self.assertEqual(timeline.frames_to_timecode(17982, 29.97), "00:10:00:00")
|
|
self.assertEqual(timeline.frames_to_timecode(24, 24), "00:00:01:00")
|
|
|
|
|
|
class EDLTests(unittest.TestCase):
|
|
def test_build_edl_header_and_events(self):
|
|
text = export.build_edl(CLIPS, fps=24.0, title="unittest-cut")
|
|
lines = text.splitlines()
|
|
self.assertEqual(lines[0], "TITLE: UNITTEST-CUT")
|
|
self.assertIn("FCM: NON-DROP FRAME", lines)
|
|
event_lines = [ln for ln in lines if ln[:1].isdigit()]
|
|
self.assertEqual(len(event_lines), 3)
|
|
self.assertIn("* FROM CLIP NAME: a.mov", text)
|
|
|
|
def test_edl_roundtrip_parses(self):
|
|
text = export.build_edl(CLIPS, fps=24.0, title="rt")
|
|
events = export.parse_edl(text)
|
|
self.assertEqual(len(events), 3)
|
|
self.assertEqual(events[0]["name"], "a.mov")
|
|
self.assertEqual(events[-1]["record_in"], 90) # 1.5+2.25 s at 24fps
|
|
self.assertEqual(events[-1]["duration_frames"], 18)
|
|
|
|
def test_ntsc_edl_is_drop_frame(self):
|
|
text = export.build_edl(CLIPS, fps=29.97, title="ntsc")
|
|
self.assertIn("FCM: DROP FRAME", text)
|
|
|
|
|
|
class FCPXMLTests(unittest.TestCase):
|
|
def test_build_fcpxml_structure(self):
|
|
text = export.build_fcpxml(CLIPS, fps=24.0, title="tf-test")
|
|
root = ET.fromstring(text)
|
|
self.assertEqual(root.tag, "fcpxml")
|
|
self.assertIn("version", root.attrib)
|
|
assets = root.findall("./resources/asset")
|
|
self.assertEqual(len(assets), 3)
|
|
clips = root.findall(".//asset-clip")
|
|
self.assertEqual(len(clips), 3)
|
|
|
|
def test_fcpxml_durations_are_rational_and_exact(self):
|
|
text = export.build_fcpxml(CLIPS, fps=24.0, title="tf-test")
|
|
root = ET.fromstring(text)
|
|
clips = root.findall(".//asset-clip")
|
|
# 1.5s @24 -> "7/2s"? No: quantized frames=36 -> "3/2s"
|
|
self.assertEqual(clips[0].get("duration"), "3/2s")
|
|
total = sum(Fraction(c.get("duration").rstrip("s")) for c in clips)
|
|
self.assertEqual(total, Fraction(int(4.5 * 24), 24))
|
|
|
|
def test_zero_or_negative_duration_rejected(self):
|
|
with self.assertRaises(ValueError):
|
|
export.build_fcpxml([{"path": "x", "duration": 0}], fps=24)
|
|
with self.assertRaises(ValueError):
|
|
export.build_edl([], fps=24)
|
|
|
|
|
|
class WriteTimelineTests(unittest.TestCase):
|
|
def test_write_timeline_emits_both_formats(self):
|
|
with tempfile.TemporaryDirectory() as td:
|
|
edl, fcpxml = export.write_timeline(CLIPS, out_dir=td, title="wt")
|
|
self.assertTrue(Path(edl).exists())
|
|
self.assertTrue(Path(fcpxml).exists())
|
|
self.assertIn("TITLE: WT", Path(edl).read_text())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|