修:draw-ui / oil-motion 原被当子模块指针收录 ⇒ 改为正常文件入库(两份内容原先对别人是空的)

一、问题(本轮实测)
`draw-ui` 与 `oil-motion` 目录里**各自带一个内嵌 `.git`** ⇒ 上一次提交把它们记成了 **gitlink(子模块指针)**
⇒ 仓库里只存了一个不属于任何远端的 commit id,**别人克隆下来这两份是空的** ✗(`git status` 显示 ` m draw-ui` / ` m oil-motion` = 子模块内容有改动)。

二、处置(可回退)
· 把两处的 `.git` **挪走**(⛔ 不是删除)⇒ `归档/内嵌git-20261008/{draw-ui,oil-motion}.git`;
· `git rm --cached` 掉那两个 gitlink,再 `git add` 两个目录 ⇒ **按正常文件入库**(内容才真的进仓库)。

三、副作用(如实记)
挪走 `.git` 后,这两个技能**不能再原地 `git pull` 取上游更新**(要更新得重新拉一份覆盖);
如需恢复其本地仓库,把 `归档/内嵌git-20261008/` 里的 `.git` 挪回原处即可。
This commit is contained in:
admin committed 2026-10-08 22:29:52 +08:00
1 parent e03465c398
commit 237a09a5b0
161 files changed
+19429 -2

No files matched your search

@@ -0,0 +1,208 @@
from __future__ import annotations
import importlib.util
import json
import sys
import tempfile
import unittest
from pathlib import Path
from PIL import Image
SCRIPT_DIR = Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(SCRIPT_DIR))
SPEC = importlib.util.spec_from_file_location(
"compile_scroll_video",
SCRIPT_DIR / "compile_scroll_video.py",
)
assert SPEC and SPEC.loader
COMPILE = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = COMPILE
SPEC.loader.exec_module(COMPILE)
from chroma_key import analyze_frame, default_parameters, key_image
class ChromaVideoCompileTests(unittest.TestCase):
def test_compiler_requires_explicit_background_owner(self) -> None:
with self.assertRaises(SystemExit):
COMPILE.parser().parse_args(
["source.mp4", "build", "--budget-report", "budget.json"]
)
def test_compiler_requires_frame_policy_and_timeline_output(self) -> None:
with self.assertRaises(SystemExit):
COMPILE.parser().parse_args(
[
"source.mp4",
"build",
"--background-owner",
"video",
"--budget-report",
"budget.json",
]
)
def test_timeline_keeps_hold_separate_from_exclusive_end(self) -> None:
specs = COMPILE.parse_segment_specs(
["first=0:2:3", "second=3:5:6"]
)
timeline = COMPILE.build_timeline(
specs,
[0, 1, 2, 4, 5, 6],
7,
24,
{"type": "constant", "rate": 1.0},
)
first = timeline["segments"][0]
self.assertEqual(timeline["initialState"], "state-0")
self.assertEqual(
[state["id"] for state in timeline["states"]],
["state-0", "first", "second"],
)
self.assertEqual(first["from"], "state-0")
self.assertEqual(first["to"], "first")
self.assertEqual(first["frames"]["hold"], 2)
self.assertEqual(first["frames"]["endExclusive"], 3)
self.assertLess(first["hold"], first["endExclusive"])
def test_representative_frames_include_both_ends(self) -> None:
paths = [Path(f"frame_{index:05d}.png") for index in range(100)]
sampled = COMPILE.representative_frames(paths)
self.assertEqual(len(sampled), 48)
self.assertEqual(sampled[0], paths[0])
self.assertEqual(sampled[-1], paths[-1])
def test_representative_indices_include_both_ends(self) -> None:
sampled = COMPILE.representative_indices(323)
self.assertEqual(len(sampled), 48)
self.assertEqual(sampled[0], 0)
self.assertEqual(sampled[-1], 322)
def test_uniform_green_frames_pass_key_validation(self) -> None:
with tempfile.TemporaryDirectory() as directory:
paths: list[Path] = []
for index in range(3):
path = Path(directory) / f"frame_{index:05d}.png"
image = Image.new("RGB", (32, 32), (0, 255, 0))
image.paste((255, 0, 0), (8, 8, 24, 24))
image.save(path)
paths.append(path)
result = COMPILE.validate_key_source(paths, (0, 255, 0))
self.assertEqual(result["kind"], "green")
self.assertEqual(result["borderSpreadP95Max"], 0)
def test_non_chroma_background_is_rejected(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "frame.png"
Image.new("RGB", (32, 32), (255, 255, 255)).save(path)
with self.assertRaisesRegex(ValueError, "绿色或洋红色键背景"):
COMPILE.validate_key_source([path], (255, 255, 255))
def test_dark_green_region_and_green_edge_are_removed(self) -> None:
with tempfile.TemporaryDirectory() as directory:
source = Path(directory) / "source.png"
output = Path(directory) / "output.png"
image = Image.new("RGB", (64, 64), (0, 235, 10))
image.paste((18, 82, 24), (8, 8, 56, 56))
image.paste((220, 40, 30), (24, 24, 40, 40))
image.save(source)
parameters = default_parameters((0, 235, 10))
key_image(source, output, parameters)
with Image.open(output) as result:
alpha = result.getchannel("A")
self.assertLessEqual(alpha.getpixel((12, 12)), 2)
self.assertGreaterEqual(alpha.getpixel((32, 32)), 250)
metrics = analyze_frame(source, parameters)
self.assertLessEqual(metrics["keyLikeAlphaP99"], 0.01)
def test_magenta_key_mode_removes_magenta_background(self) -> None:
with tempfile.TemporaryDirectory() as directory:
source = Path(directory) / "source.png"
output = Path(directory) / "output.png"
image = Image.new("RGB", (32, 32), (240, 0, 235))
image.paste((30, 180, 220), (8, 8, 24, 24))
image.save(source)
parameters = default_parameters((240, 0, 235))
key_image(source, output, parameters)
self.assertEqual(parameters.mode, "magenta")
with Image.open(output) as result:
alpha = result.getchannel("A")
self.assertLessEqual(alpha.getpixel((2, 2)), 2)
self.assertGreaterEqual(alpha.getpixel((16, 16)), 250)
def test_source_anchor_maps_to_nearest_retained_frame(self) -> None:
kept = [0, 1, 4, 9, 20, 48, 90, 248, 251]
self.assertEqual(COMPILE.map_source_frame(248, kept), 7)
self.assertEqual(COMPILE.map_source_frame(47, kept), 5)
def test_anchor_parser_rejects_duplicates(self) -> None:
with self.assertRaisesRegex(ValueError, "重复"):
COMPILE.parse_anchors(["center=20", "center=21"])
def test_runtime_shader_implements_manifest_contract(self) -> None:
shader = (
SCRIPT_DIR.parent / "assets" / "chroma-video-renderer.ts"
).read_text(encoding="utf-8")
for token in (
'algorithm: "dominance-v2"',
"uDominanceStart",
"uDominanceEnd",
"uSpillStart",
"uSpillEnd",
"keying.keyColor",
):
self.assertIn(token, shader)
def test_video_compiler_rejects_atlas_budget_report(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "budget.json"
path.write_text(
json.dumps(
{
"passes": True,
"delivery": {"selected": "alpha-atlas"},
}
),
encoding="utf-8",
)
with self.assertRaisesRegex(ValueError, "没有选择 chroma-video"):
COMPILE.load_budget_report(path)
def test_baked_compiler_accepts_baked_budget_report(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "budget.json"
path.write_text(
json.dumps(
{
"passes": True,
"delivery": {"selected": "baked-video"},
}
),
encoding="utf-8",
)
report = COMPILE.load_budget_report(path, "baked-video")
self.assertEqual(report["delivery"]["selected"], "baked-video")
with self.assertRaisesRegex(ValueError, "没有选择 chroma-video"):
COMPILE.load_budget_report(path)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,29 @@
import io
import json
import os
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import patch
from contextlib import redirect_stdout
sys.path.insert(0,str(Path(__file__).parents[1]/'scripts'))
import oil_motion_config as config
class ConfigTests(unittest.TestCase):
def test_main_replaces_legacy_secret_with_reference(self):
with tempfile.TemporaryDirectory() as tmp:
path=Path(tmp)/'config.json';path.write_text(json.dumps({'zenmux':{'api_key':'old-fake','model':'keep'}}))
with patch.dict(os.environ,{'OIL_MOTION_CONFIG_FILE':str(path)}),patch.object(sys,'argv',['config','set']),patch.object(config.getpass,'getpass',return_value='new-fake'),patch.object(config.secure_credentials,'save') as save,redirect_stdout(io.StringIO()):
self.assertEqual(config.main(),0)
save.assert_called_once_with(config.CREDENTIAL_REF,'new-fake')
data=json.loads(path.read_text());self.assertEqual(data['zenmux']['model'],'keep')
self.assertEqual(data['zenmux']['credential_ref'],config.CREDENTIAL_REF)
self.assertNotIn('fake',path.read_text())
def test_store_failure_keeps_original_config(self):
with tempfile.TemporaryDirectory() as tmp:
path=Path(tmp)/'config.json';path.write_text('{"zenmux":{"model":"keep"}}')
before=path.read_text()
with patch.object(config.getpass,'getpass',return_value='new-fake'),patch.object(config.secure_credentials,'save',side_effect=RuntimeError('unavailable')),self.assertRaises(RuntimeError):
config.set_api_key(path)
self.assertEqual(path.read_text(),before)
if __name__ == '__main__':unittest.main()
+393
View File
@@ -0,0 +1,393 @@
from __future__ import annotations
import json
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DOC_PATHS = [ROOT / "SKILL.md", *sorted((ROOT / "references").glob("*.md"))]
def read(relative: str) -> str:
# 文档按行宽换行,断言基于去掉换行后的连续文本
return (ROOT / relative).read_text(encoding="utf-8").replace("\n", "")
def bash_blocks(path: Path) -> list[str]:
source = path.read_text(encoding="utf-8")
return re.findall(r"^```bash\n(.*?)^```", source, re.S | re.M)
def files_with_bash(snippet: str) -> list[str]:
return sorted(
path.name
for path in DOC_PATHS
if any(snippet in block for block in bash_blocks(path))
)
class DocsConsistencyTests(unittest.TestCase):
"""文档架构回归:唯一事实源、链接有效、关键语义与脚本能力。
每个语义只在它的事实源文件中断言一次,不依赖跨文件重复的固定句。
"""
# ---------- SKILL.md:主流程与关键防回归语义 ----------
def test_skill_defines_concept_contract_gate(self) -> None:
skill = read("SKILL.md")
self.assertIn("Concept Contract", skill)
self.assertIn("subject_count", skill)
self.assertIn("background_owner", skill)
self.assertIn("input_semantics", skill)
self.assertIn("time_control", skill)
self.assertIn("navigation", skill)
self.assertIn("clip_continuity", skill)
self.assertIn("不改写、不扩写", skill)
def test_key_anti_regression_semantics_have_canonical_homes(self) -> None:
skill = read("SKILL.md")
qa = read("references/qa.md")
chroma = read("references/chroma-video.md")
# 场景背景默认与视频一起生成(baked 默认)
self.assertIn("baked-video", skill)
self.assertIn("background_owner: video", skill)
# 只有明确透明复用才用 chroma
self.assertIn("主体必须透明复用在页面背景", skill)
# 多段同脸与实际尾帧接力
self.assertIn("同脸", qa)
self.assertIn("实际尾帧", qa)
# Pilot 通过后才量产
self.assertIn("Pilot 硬门", qa)
self.assertIn("approve-pilot", qa)
# 坏绿幕直接拒收
self.assertIn("拒收母版", chroma)
def test_motion_brief_does_not_duplicate_contract_fields(self) -> None:
source = (ROOT / "SKILL.md").read_text(encoding="utf-8")
section = source.split("建立生产计划", 1)[1].split("### 3.", 1)[0]
block = re.search(r"```yaml\n(.*?)```", section, re.S)
self.assertIsNotNone(block, "Motion Brief 缺少 yaml 模板")
brief = block.group(1)
top_level_fields = set(re.findall(r"^([a-z_]+):", brief, re.M))
for field in (
"subject_count",
"background_owner",
"clip_continuity",
"driver",
"input_semantics",
"time_control",
"navigation",
"scene",
"destination",
"aspect_ratio",
):
self.assertNotIn(
field, top_level_fields, f"Motion Brief 复制了合同字段 {field}"
)
def test_skill_stays_within_line_budget(self) -> None:
lines = (ROOT / "SKILL.md").read_text(encoding="utf-8").splitlines()
self.assertLessEqual(len(lines), 200)
def test_skill_has_no_parallel_principles_flow(self) -> None:
source = (ROOT / "SKILL.md").read_text(encoding="utf-8")
self.assertNotIn("## 核心原则", source)
self.assertEqual(source.count("## 主流程"), 1)
def test_skill_separates_input_navigation_and_time_control(self) -> None:
skill = read("SKILL.md")
runtime = read("references/runtime.md")
self.assertIn("格式选择与播放方式是两件事", skill)
self.assertIn("分页页面也可以使用连续时间轴", skill)
self.assertIn("滚动既可以 scrub,也可以触发片段播放", runtime)
self.assertIn("start <= hold < endExclusive", runtime)
self.assertIn("one-gesture-one-step", runtime)
self.assertIn("programmatic_navigation=ignore", runtime)
self.assertIn("timeline.json.states[].id", runtime)
def test_skill_no_longer_mandates_chroma_for_everything(self) -> None:
skill = read("SKILL.md")
self.assertNotIn("固定绿幕管线", skill)
self.assertNotIn("不存在“直接生成最终场景背景”的分支", skill)
# 旧 Motion Brief 无条件要求 matte,新模板按背景归属分路线
self.assertNotIn("matte_delivery: required", skill)
def test_skill_routes_to_every_reference(self) -> None:
skill = read("SKILL.md")
for ref in sorted((ROOT / "references").glob("*.md")):
self.assertIn(
f"references/{ref.name}", skill, f"SKILL.md 没有路由到 {ref.name}"
)
# ---------- 唯一事实源:命令示例只存在于一个 canonical reference ----------
def test_command_examples_have_single_canonical_home(self) -> None:
expectations = {
"motion_budget.py": ["delivery-selection.md"],
"video_job.py": ["prompting.md"],
"image_job.py": ["prompting.md"],
"composite_alpha_keyframe.py": ["prompting.md"],
"compose_travel_frames.py": ["prompting.md"],
"approve-pilot": ["qa.md"],
"verify-chain": ["qa.md"],
"compile_scroll_video.py": ["baked-video.md", "chroma-video.md"],
"optimize_motion.py": ["optimization.md"],
"loop_cleanup.py": ["alpha-atlas.md"],
"motion_pipeline.py": ["alpha-atlas.md"],
"create_explainer.py": ["explainer.md"],
"oil_motion_config.py": [], # 原终端入口不再作为默认命令示例
}
for snippet, expected in expectations.items():
self.assertEqual(
files_with_bash(snippet),
expected,
f"{snippet} 的命令示例不在唯一事实源 {expected}",
)
def test_page_setup_and_real_video_submission_share_profile(self) -> None:
skill = read("SKILL.md")
self.assertIn("profile.ts\" setup default", skill)
self.assertIn("profile.ts\" status default", skill)
prompting = read("references/prompting.md")
for script in ("image_job.py", "video_job.py"):
self.assertIn(f"profile.ts\" run default -- python3 \"$OIL_MOTION/scripts/{script}\"", prompting)
def test_keyframes_use_bundled_generator_not_host_tool(self) -> None:
for path in [ROOT / "SKILL.md", *sorted((ROOT / "references").glob("*.md"))]:
self.assertNotIn("$imagegen", path.read_text(encoding="utf-8"), f"{path.name} 依赖宿主专属生图工具")
skill = read("SKILL.md")
self.assertIn("--background transparent", skill)
self.assertIn("--background opaque", skill)
self.assertNotIn("1792x1024", read("references/prompting.md"))
def test_canonical_sections_not_duplicated(self) -> None:
docs = {path.name: path.read_text(encoding="utf-8") for path in DOC_PATHS}
for marker, home in (
("固定决策顺序", "delivery-selection.md"),
("逐帧模拟运行时抠色", "chroma-video.md"),
("postEncodeKeyingPassed", "chroma-video.md"),
("扩大抠色阈值", "chroma-video.md"),
("真实页面最终位置挂载", "qa.md"),
("不接受“差不多”", "qa.md"),
("不允许误差逐段累积", "qa.md"),
("接口错误 `2013`", "prompting.md"),
):
homes = sorted(name for name, text in docs.items() if marker in text)
self.assertEqual(homes, [home], f"{marker} 应只属于 {home}")
def test_no_exact_duplicate_prose_paragraphs(self) -> None:
seen: dict[str, str] = {}
for path in DOC_PATHS:
source = path.read_text(encoding="utf-8")
for paragraph in re.split(r"\n\s*\n", source):
normalized = re.sub(r"\s+", " ", paragraph).strip()
if len(normalized) < 120:
continue
previous = seen.get(normalized)
self.assertIsNone(
previous,
f"{path.name} 与 {previous} 存在完全重复段落",
)
seen[normalized] = path.name
# ---------- 职责边界:各文档不复述其他事实源的段落 ----------
def test_prompting_keeps_params_but_not_gate_algorithms(self) -> None:
doc = read("references/prompting.md")
# 提交参数是 prompting 的职责
self.assertIn("--loop-frame", doc)
self.assertIn("--seed", doc)
self.assertIn("--continuity-mode chain", doc)
# 公共工作流与 Pilot/帧链算法不复述,只链接
self.assertNotIn("固定工作流", doc)
self.assertNotIn("在联网前阻断", doc)
self.assertNotIn("SHA-256 完全一致", doc)
def test_minimax_keeps_only_atlas_specifics(self) -> None:
doc = read("references/alpha-atlas.md")
# 图集路线专属命令仍在
self.assertIn("loop_cleanup.py", doc)
self.assertIn("atlas", doc)
# 首尾模式表与公共提交/验收复述已删除
self.assertNotIn("仅参考生成", doc)
self.assertNotIn("母版门槛", doc)
self.assertNotIn("video_job.py", doc)
# 公共步骤只链接,不复述清单
self.assertNotIn("完整观看视频", doc)
def test_skill_flow_steps_link_instead_of_repeating(self) -> None:
skill = read("SKILL.md")
# Pilot 四项清单、帧链算法、抠色算法只存在于 qa / chroma-video
self.assertNotIn("真实页面最终位置挂载", skill)
self.assertNotIn("qa/frame-chain.json` 证明", skill)
self.assertNotIn("similarity", skill)
self.assertNotIn("smoothDamp", skill)
self.assertNotIn("同脸、同发型", skill)
self.assertNotIn("拒收母版", skill)
self.assertNotIn("SHA-256 完全一致", skill)
self.assertNotIn("48 FPS", skill)
# ---------- 链接有效 ----------
def test_markdown_links_resolve(self) -> None:
for path in DOC_PATHS:
source = path.read_text(encoding="utf-8")
for target in re.findall(r"\]\(([^)]+)\)", source):
if target.startswith(("http://", "https://", "#", "mailto:")):
continue
target = target.split("#", 1)[0]
resolved = (path.parent / target).resolve()
self.assertTrue(
resolved.exists(), f"{path.name} 的链接失效: {target}"
)
def test_no_reference_is_orphaned(self) -> None:
for ref in sorted((ROOT / "references").glob("*.md")):
others = "\n".join(
path.read_text(encoding="utf-8")
for path in DOC_PATHS
if path != ref
)
self.assertIn(ref.name, others, f"{ref.name} 没有被任何文档链接")
# ---------- 各 canonical reference 的关键语义 ----------
def test_delivery_selection_owns_route_selection(self) -> None:
doc = read("references/delivery-selection.md")
self.assertIn("baked-video", doc)
self.assertIn("--background-owner", doc)
self.assertIn("delivery.selected", doc)
self.assertIn("固定决策顺序", doc)
self.assertIn("runtime.controller", doc)
def test_chroma_route_owns_reject_first_matte_gate(self) -> None:
doc = read("references/chroma-video.md")
self.assertIn("逐帧模拟运行时抠色", doc)
self.assertIn("runtime.keying", doc)
self.assertIn("溢色", doc)
self.assertIn("拒收", doc)
self.assertIn("禁止靠扩大抠色阈值", doc)
self.assertIn("background-matrix", doc)
def test_baked_route_reference(self) -> None:
doc = read("references/baked-video.md")
self.assertIn("background_owner: video", doc)
self.assertIn("不做抠色", doc)
def test_prompting_owns_submission_and_prompt_sections(self) -> None:
doc = read("references/prompting.md")
self.assertIn("Concept Contract", doc)
self.assertIn("Identity Bible", doc)
self.assertIn("场景背景段(baked 路线)", doc)
self.assertIn("视频色键段(page 路线)", doc)
self.assertIn("直接生成真实透明背景 PNG", doc)
self.assertIn("composite_alpha_keyframe.py", doc)
self.assertIn("--stage", doc)
self.assertIn("--continuity-mode chain", doc)
def test_qa_owns_gates_and_acceptance(self) -> None:
doc = read("references/qa.md")
self.assertIn("Concept Contract 回归", doc)
self.assertIn("Identity Bible", doc)
self.assertIn("连续帧链", doc)
self.assertIn("Pilot 硬门", doc)
self.assertIn("frame-chain.json", doc)
self.assertIn("pilot/approval.json", doc)
self.assertIn("verify-output-chain", doc)
# ---------- 脚本能力与文档一致 ----------
def test_production_gates_are_executable(self) -> None:
qa = read("references/qa.md")
script = read("scripts/production_gate.py")
video_job = read("scripts/video_job.py")
budget = read("scripts/motion_budget.py")
compiler = read("scripts/compile_scroll_video.py")
self.assertIn("pilot/approval.json", qa)
self.assertIn("qa/frame-chain.json", qa)
self.assertIn("approve-pilot", script)
self.assertIn("verify-chain", script)
self.assertIn("verify-output-chain", script)
self.assertIn("contract_continuity_mode", script)
self.assertIn("--stage", video_job)
self.assertIn("validate_pilot_approval", video_job)
self.assertIn("--background-owner", budget)
self.assertIn("--background-owner", compiler)
self.assertIn("postEncodeKeyingReport", compiler)
self.assertIn("checkedFrames", compiler)
def test_runtime_assets_cover_both_time_control_modes(self) -> None:
interactive = read("assets/interactive-motion.ts")
chroma = read("assets/chroma-video-renderer.ts")
gesture = read("assets/step-gesture.ts")
self.assertIn("createFrameAnimator", interactive)
self.assertIn("createSegmentPlayer", interactive)
self.assertIn("start <= hold < endExclusive", interactive)
self.assertNotIn("timeupdate", interactive)
self.assertIn("startLive", chroma)
self.assertIn("stopLive", chroma)
self.assertIn("createStepGestureAdapter", gesture)
self.assertIn("setProgrammaticNavigation", gesture)
def test_command_examples_do_not_omit_hard_gate_arguments(self) -> None:
for path in DOC_PATHS:
for block in bash_blocks(path):
if "motion_budget.py" in block:
self.assertIn(
"--background-owner",
block,
f"{path.name} 的预算命令漏传背景归属",
)
self.assertIn(
"--time-control",
block,
f"{path.name} 的预算命令漏传时间控制",
)
if "compile_scroll_video.py" in block:
self.assertIn(
"--background-owner",
block,
f"{path.name} 的编译命令漏传背景归属",
)
self.assertIn(
"--frame-policy",
block,
f"{path.name} 的编译命令漏传帧策略",
)
self.assertIn(
"--timeline-output",
block,
f"{path.name} 的编译命令漏传时间轴输出",
)
if "video_job.py" in block:
self.assertIn(
"--stage",
block,
f"{path.name} 的生成命令漏传 Pilot/production 阶段",
)
# ---------- evals 覆盖关键场景 ----------
def test_evals_cover_single_anime_baked_case(self) -> None:
evals = json.loads(read("evals/evals.json"))["evals"]
prompts = [item["prompt"] for item in evals]
expected = [item["expected_output"] for item in evals]
self.assertTrue(
any("单人" in p and "动漫" in p for p in prompts),
"evals 必须包含单人动漫场景用例",
)
self.assertTrue(
any("Concept Contract" in e and "baked-video" in e for e in expected),
"evals 必须断言 Concept Contract 与 baked-video 路线",
)
self.assertTrue(
any("拒绝" in e and "阈值" in e for e in expected),
"evals 必须断言禁止用调阈值掩盖抠色缺陷",
)
if __name__ == "__main__":
unittest.main()
+154
View File
@@ -0,0 +1,154 @@
from __future__ import annotations
import base64
import contextlib
import io
import json
import os
import subprocess
import sys
import tempfile
import unittest
import urllib.error
from pathlib import Path
from unittest import mock
from PIL import Image
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "image_job.py"
sys.path.insert(0, str(SCRIPT.parent))
import image_job
FAKE_KEY = "FAKE-ZENMUX-" + "0" * 16
def png_bytes(mode: str, corner_alpha: int = 0) -> bytes:
if mode == "RGBA":
image = Image.new("RGBA", (64, 64), (0, 0, 0, corner_alpha))
for x in range(16, 48):
for y in range(16, 48):
image.putpixel((x, y), (200, 80, 40, 255))
else:
image = Image.new("RGB", (64, 64), (120, 160, 200))
buffer = io.BytesIO()
image.save(buffer, format="PNG")
return buffer.getvalue()
class FakeResponse:
def __init__(self, payload: dict) -> None:
self.body = json.dumps(payload).encode("utf-8")
def read(self) -> bytes:
return self.body
def __enter__(self) -> "FakeResponse":
return self
def __exit__(self, *args) -> None:
return None
class ImageJobTests(unittest.TestCase):
def setUp(self) -> None:
self.temporary = tempfile.TemporaryDirectory()
self.root = Path(self.temporary.name)
def tearDown(self) -> None:
self.temporary.cleanup()
def generate(self, image: bytes, background: str, references: list[Path] | None = None):
requests = []
def fake_urlopen(request, timeout=0):
requests.append(request)
return FakeResponse({"data": [{"b64_json": base64.b64encode(image).decode("ascii")}]})
output = self.root / "K0.png"
with mock.patch.object(image_job, "require_api_key", return_value=FAKE_KEY), \
mock.patch.object(image_job.urllib.request, "urlopen", side_effect=fake_urlopen), \
contextlib.redirect_stdout(io.StringIO()):
result = image_job.generate_image("测试", output, background=background, references=references)
return result, requests
def test_background_is_required_on_cli(self):
run = subprocess.run(
[sys.executable, str(SCRIPT), "--prompt", "x", "--output", str(self.root / "a.png"), "--dry-run"],
capture_output=True, text=True, env={**os.environ, "ZENMUX_API_KEY": ""},
)
self.assertNotEqual(run.returncode, 0)
self.assertIn("--background", run.stderr)
def test_dry_run_needs_no_key_and_routes_references_to_edits(self):
reference = self.root / "ref.png"
reference.write_bytes(png_bytes("RGB"))
base = [sys.executable, str(SCRIPT), "--prompt", "x", "--output", str(self.root / "a.png"), "--background", "opaque", "--dry-run"]
env = {key: value for key, value in os.environ.items() if key != "ZENMUX_API_KEY"}
generations = json.loads(subprocess.run(base, capture_output=True, text=True, check=True, env=env).stdout)
edits = json.loads(subprocess.run([*base, "--image", str(reference)], capture_output=True, text=True, check=True, env=env).stdout)
self.assertTrue(generations["endpoint"].endswith("/images/generations"))
self.assertTrue(edits["endpoint"].endswith("/images/edits"))
self.assertEqual(edits["background"], "opaque")
self.assertEqual(edits["references"], [str(reference.resolve())])
def test_size_follows_model_constraints(self):
self.assertEqual(image_job.parse_size("2048x1152", image_job.DEFAULT_IMAGE_MODEL), (2048, 1152))
for bad in ("1000x1000", "4096x1024", "512x512", "wide"):
with self.assertRaises(ValueError):
image_job.parse_size(bad, image_job.DEFAULT_IMAGE_MODEL)
def test_transparent_keyframe_with_real_alpha_is_accepted(self):
result, requests = self.generate(png_bytes("RGBA", corner_alpha=0), "transparent")
self.assertEqual(result.name, "K0.png")
payload = json.loads(requests[0].data)
self.assertEqual(payload["background"], "transparent")
self.assertNotIn("transparent background", payload["prompt"])
with Image.open(result) as saved:
self.assertIn("A", saved.getbands())
def test_transparent_request_without_real_alpha_is_rejected(self):
for image in (png_bytes("RGB"), png_bytes("RGBA", corner_alpha=255)):
for leftover in self.root.glob("K0*"):
leftover.unlink()
with self.assertRaises(RuntimeError) as raised:
self.generate(image, "transparent")
self.assertIn("透明验收失败", str(raised.exception))
self.assertFalse((self.root / "K0.png").exists())
self.assertTrue((self.root / "K0.rejected.png").exists())
def test_opaque_scene_keyframe_skips_alpha_gate(self):
result, requests = self.generate(png_bytes("RGB"), "opaque")
self.assertEqual(result.name, "K0.png")
self.assertEqual(json.loads(requests[0].data)["background"], "opaque")
def test_references_use_multipart_edits(self):
reference = self.root / "product.png"
reference.write_bytes(png_bytes("RGB"))
_, requests = self.generate(png_bytes("RGB"), "opaque", references=[reference])
request = requests[0]
self.assertTrue(request.full_url.endswith("/images/edits"))
self.assertIn("multipart/form-data", request.get_header("Content-type"))
self.assertIn(b'name="image[]"; filename="product.png"', request.data)
def test_refuses_to_overwrite_existing_output(self):
(self.root / "K0.png").write_bytes(b"keep")
with mock.patch.object(image_job, "require_api_key", side_effect=AssertionError("不应读取密钥")):
with self.assertRaises(FileExistsError):
image_job.generate_image("测试", self.root / "K0.png", background="opaque")
self.assertEqual((self.root / "K0.png").read_bytes(), b"keep")
def test_http_error_does_not_echo_key(self):
fake_key = FAKE_KEY
error = urllib.error.HTTPError("https://zenmux.ai", 401, "denied", {}, io.BytesIO(f"bad key {fake_key}".encode()))
self.addCleanup(error.close)
with mock.patch.object(image_job, "require_api_key", return_value=fake_key), \
mock.patch.object(image_job.urllib.request, "urlopen", side_effect=error), \
contextlib.redirect_stdout(io.StringIO()):
with self.assertRaises(RuntimeError) as raised:
image_job.generate_image("测试", self.root / "K0.png", background="opaque")
self.assertNotIn(fake_key, str(raised.exception))
if __name__ == "__main__":
unittest.main()
+232
View File
@@ -0,0 +1,232 @@
from __future__ import annotations
import importlib.util
import subprocess
import sys
import unittest
from argparse import Namespace
from pathlib import Path
MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "motion_budget.py"
SPEC = importlib.util.spec_from_file_location("motion_budget", MODULE_PATH)
assert SPEC and SPEC.loader
MOTION_BUDGET = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = MOTION_BUDGET
SPEC.loader.exec_module(MOTION_BUDGET)
def arguments(**overrides: object) -> Namespace:
values: dict[str, object] = {
"frames": 96,
"display": (240, 240),
"dpr": 1.0,
"max_texture": 4096,
"driver": "pointer",
"time_control": "scrub",
"parameter_space": "circular",
"access": "auto",
"background_owner": "page",
"atlas_max_memory_mib": 192.0,
"linear_video_min_frames": 180,
"source": None,
"cell": None,
"scroll_pages": None,
"frames_per_page": 24.0,
}
values.update(overrides)
return Namespace(**values)
class MotionBudgetSelectionTests(unittest.TestCase):
def test_cli_requires_explicit_background_owner(self) -> None:
completed = subprocess.run(
[
sys.executable,
str(MODULE_PATH),
"--frames",
"240",
"--display",
"1280x720",
"--strict",
],
capture_output=True,
text=True,
)
self.assertNotEqual(completed.returncode, 0)
self.assertIn("--background-owner", completed.stderr)
def test_small_random_sequence_selects_alpha_atlas(self) -> None:
report = MOTION_BUDGET.build_report(arguments())
self.assertEqual(report["delivery"]["selected"], "alpha-atlas")
self.assertEqual(report["runtime"]["renderer"], "css-alpha-atlas")
self.assertEqual(report["runtime"]["controller"], "frame-scrub")
self.assertTrue(report["passes"])
def test_large_circular_sequence_falls_back_to_chroma_video(self) -> None:
report = MOTION_BUDGET.build_report(
arguments(
frames=48,
display=(640, 640),
parameter_space="circular",
)
)
self.assertEqual(report["texture"]["columnsPerSheet"], 6)
self.assertEqual(report["texture"]["rowsPerSheet"], 6)
self.assertEqual(report["texture"]["framesPerSheet"], 36)
self.assertEqual(report["texture"]["sheetCount"], 2)
self.assertEqual(report["delivery"]["selected"], "chroma-video")
self.assertIn(
"atlas-budget-exceeded",
report["delivery"]["reasonCodes"],
)
self.assertTrue(report["passes"])
def test_large_linear_scroll_selects_chroma_video(self) -> None:
report = MOTION_BUDGET.build_report(
arguments(
frames=551,
display=(1536, 864),
driver="scroll",
parameter_space="linear",
)
)
self.assertEqual(report["delivery"]["selected"], "chroma-video")
self.assertEqual(report["runtime"]["renderer"], "webgl-chroma-video")
self.assertEqual(report["runtime"]["controller"], "frame-scrub")
self.assertIn("long-linear-sequence", report["delivery"]["reasonCodes"])
self.assertTrue(report["passes"])
def test_two_dimensional_input_is_never_flattened_to_video(self) -> None:
report = MOTION_BUDGET.build_report(
arguments(
frames=400,
display=(900, 900),
parameter_space="2d",
)
)
self.assertEqual(report["delivery"]["selected"], "alpha-atlas")
self.assertIn(
"two-dimensional-parameter-needs-discrete-frames",
report["delivery"]["reasonCodes"],
)
self.assertTrue(
any(
"拆分参数轴" in failure and "用户确认" in failure
for failure in report["failures"]
)
)
self.assertFalse(report["passes"])
def test_discrete_states_are_split_instead_of_flattened(self) -> None:
report = MOTION_BUDGET.build_report(
arguments(
frames=400,
display=(900, 900),
driver="state",
parameter_space="discrete",
)
)
self.assertEqual(report["delivery"]["selected"], "alpha-atlas")
self.assertIn(
"discrete-states-need-independent-assets",
report["delivery"]["reasonCodes"],
)
self.assertFalse(report["passes"])
def test_baked_scene_video_when_background_belongs_to_video(self) -> None:
report = MOTION_BUDGET.build_report(
arguments(
frames=551,
display=(1536, 864),
driver="scroll",
parameter_space="linear",
background_owner="video",
)
)
self.assertEqual(report["delivery"]["selected"], "baked-video")
self.assertEqual(report["runtime"]["renderer"], "baked-video")
self.assertEqual(report["runtime"]["controller"], "frame-scrub")
self.assertIn(
"background-baked-into-video",
report["delivery"]["reasonCodes"],
)
self.assertIn("long-linear-sequence", report["delivery"]["reasonCodes"])
self.assertEqual(report["backgroundOwner"], "video")
self.assertTrue(report["passes"])
def test_scroll_segment_play_is_not_misclassified_as_scrub(self) -> None:
report = MOTION_BUDGET.build_report(
arguments(
frames=240,
display=(1280, 720),
driver="scroll",
time_control="segment-play",
parameter_space="linear",
background_owner="video",
)
)
self.assertEqual(report["delivery"]["selected"], "baked-video")
self.assertEqual(report["runtime"]["controller"], "segment-playback")
self.assertEqual(report["access"], "sequential")
def test_cli_requires_explicit_time_control(self) -> None:
completed = subprocess.run(
[
sys.executable,
str(MODULE_PATH),
"--frames",
"240",
"--display",
"1280x720",
"--background-owner",
"video",
],
capture_output=True,
text=True,
)
self.assertNotEqual(completed.returncode, 0)
self.assertIn("--time-control", completed.stderr)
def test_baked_video_is_not_failed_by_atlas_budget(self) -> None:
report = MOTION_BUDGET.build_report(
arguments(
frames=900,
display=(900, 900),
parameter_space="circular",
background_owner="video",
)
)
self.assertEqual(report["delivery"]["selected"], "baked-video")
self.assertTrue(report["passes"])
def test_baked_video_rejects_flattened_two_dimensional_input(self) -> None:
report = MOTION_BUDGET.build_report(
arguments(
frames=400,
display=(900, 900),
parameter_space="2d",
background_owner="video",
)
)
self.assertEqual(report["delivery"]["selected"], "baked-video")
self.assertIn(
"baked-video-needs-independent-clips",
report["delivery"]["reasonCodes"],
)
self.assertFalse(report["passes"])
if __name__ == "__main__":
unittest.main()
+252
View File
@@ -0,0 +1,252 @@
from __future__ import annotations
import importlib.util
import json
import shutil
import subprocess
import sys
import tempfile
import unittest
from argparse import Namespace
from pathlib import Path
from PIL import Image
SCRIPT_DIR = Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(SCRIPT_DIR))
def load(name: str):
spec = importlib.util.spec_from_file_location(name, SCRIPT_DIR / f"{name}.py")
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
GATE = load("production_gate")
VIDEO = load("video_job")
FFMPEG = shutil.which("ffmpeg")
def write(path: Path, value: bytes) -> Path:
path.write_bytes(value)
return path
def make_color_video(path: Path, color: str) -> None:
subprocess.run(
[
FFMPEG or "ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-y",
"-f",
"lavfi",
"-i",
f"color={color}:size=64x64:rate=12:duration=1",
"-pix_fmt",
"yuv420p",
str(path),
],
check=True,
)
class ProductionGateTests(unittest.TestCase):
def approval(self, root: Path, continuity: str = "chain") -> Path:
files = {
"contract": write(
root / "contract.yaml",
f"subject_count: 1\nclip_continuity: {continuity}\n".encode(),
),
"first_frame": write(root / "first.png", b"first"),
"last_frame": write(root / "last.png", b"last"),
"video": write(root / "pilot.mp4", b"video"),
"page_evidence": write(root / "page.png", b"page"),
}
output = root / "approval.json"
GATE.create_pilot_approval(
Namespace(
**files,
identity_bible=None,
reviewer="visual-review",
notes="passed",
decision="pass",
output=output,
force=False,
)
)
return output
def test_pilot_approval_detects_changed_artifact(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
approval = self.approval(root)
GATE.validate_pilot_approval(approval)
# Updating pageEvidence does not invalidate pilot approval as long as file exists
(root / "page.png").write_bytes(b"updated screenshot")
self.assertTrue(GATE.validate_pilot_approval(approval)["passed"])
# Changing core generation artifact invalidates pilot approval
(root / "first.png").write_bytes(b"changed")
with self.assertRaisesRegex(ValueError, "批准失效"):
GATE.validate_pilot_approval(approval)
def test_contract_continuity_requires_unique_top_level_value(self) -> None:
invalid_contracts = {
"nested": "metadata:\n clip_continuity: independent\n",
"duplicate": (
"clip_continuity: chain\n"
"clip_continuity: independent\n"
),
"string": 'notes: "clip_continuity: independent"\n',
"comment": "# clip_continuity: independent\nsubject_count: 1\n",
"missing": "subject_count: 1\n",
}
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for name, content in invalid_contracts.items():
with self.subTest(name=name):
path = root / f"{name}.yaml"
path.write_text(content, encoding="utf-8")
with self.assertRaises(ValueError):
GATE.contract_continuity_mode(path)
valid = root / "valid.yaml"
valid.write_text(
"subject_count: 1\nclip_continuity: chain # required\n",
encoding="utf-8",
)
self.assertEqual(GATE.contract_continuity_mode(valid), "chain")
def test_frame_chain_records_exact_hash_match(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
previous = write(root / "tail.png", b"same-frame")
current = write(root / "first.png", b"same-frame")
manifest = root / "frame-chain.json"
link = GATE.verify_frame_chain(previous, current, 2, manifest)
self.assertTrue(link["exactSha256Match"])
payload = json.loads(manifest.read_text(encoding="utf-8"))
self.assertEqual(payload["links"][0]["segmentIndex"], 2)
def test_frame_chain_rejects_modified_next_first(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
previous = write(root / "tail.png", b"tail")
current = write(root / "first.png", b"not-tail")
with self.assertRaisesRegex(ValueError, "连续帧链断裂"):
GATE.verify_frame_chain(previous, current, 2)
def test_frame_similarity_rejects_visible_change(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
first = root / "first.png"
second = root / "second.png"
Image.new("RGB", (32, 32), (255, 0, 0)).save(first)
Image.new("RGB", (32, 32), (0, 0, 255)).save(second)
metrics = GATE.frame_similarity(first, second)
self.assertLess(metrics["ssim"], 0.97)
self.assertGreater(metrics["normalizedMae"], 0.04)
@unittest.skipUnless(FFMPEG, "需要 ffmpeg 才能验证成片接缝")
def test_output_chain_checks_decoded_video_edges(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
previous = root / "previous.mp4"
current = root / "current.mp4"
make_color_video(previous, "red")
make_color_video(current, "red")
result = GATE.verify_output_chain(
previous,
current,
2,
root / "frame-chain.json",
root / "evidence",
)
self.assertTrue(result["passed"])
self.assertIn("ssim", result["metrics"])
def test_video_production_requires_pilot_approval(self) -> None:
args = Namespace(
stage="production",
segment_index=2,
pilot_approval=None,
continuity_mode="independent",
previous_tail=None,
first_frame=None,
frame_chain_manifest=None,
)
with self.assertRaisesRegex(ValueError, "pilot-approval"):
VIDEO.validate_production_gate(args)
def test_video_chain_requires_exact_previous_tail(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
approval = self.approval(root)
previous = write(root / "tail.png", b"tail")
current = write(root / "next.png", b"different")
args = Namespace(
stage="production",
segment_index=2,
pilot_approval=approval,
continuity_mode="chain",
previous_tail=previous,
first_frame=current,
frame_chain_manifest=root / "frame-chain.json",
)
with self.assertRaisesRegex(ValueError, "连续帧链断裂"):
VIDEO.validate_production_gate(args)
def test_continuous_contract_rejects_independent_bypass(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
approval = self.approval(root, "chain")
args = Namespace(
stage="production",
segment_index=2,
pilot_approval=approval,
continuity_mode="independent",
previous_tail=None,
first_frame=None,
frame_chain_manifest=None,
)
with self.assertRaisesRegex(ValueError, "合同要求 chain"):
VIDEO.validate_production_gate(args)
def test_independent_contract_allows_independent_production(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
approval = self.approval(root, "independent")
args = Namespace(
stage="production",
segment_index=2,
pilot_approval=approval,
continuity_mode="independent",
previous_tail=None,
first_frame=None,
frame_chain_manifest=None,
)
result = VIDEO.validate_production_gate(args)
self.assertEqual(result["continuityMode"], "independent")
if __name__ == "__main__":
unittest.main()
+29
View File
@@ -0,0 +1,29 @@
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'scripts'))
import oil_motion_config as config
class BusinessCredentialTests(unittest.TestCase):
def test_profile_environment_reaches_business_reader(self):
with patch.dict(os.environ, {'ZENMUX_API_KEY': 'TEST_KEY'}, clear=True), patch.object(Path, 'read_text', side_effect=AssertionError('不应读取旧凭据文件')):
self.assertEqual(config.require_api_key(), 'TEST_KEY')
def test_missing_key_points_to_page_and_run_entry(self):
with tempfile.TemporaryDirectory() as tmp, patch.dict(os.environ, {'OIL_MOTION_CONFIG_FILE': str(Path(tmp) / 'config.json')}, clear=True):
with self.assertRaises(RuntimeError) as raised:
config.require_api_key()
message = str(raised.exception)
self.assertIn('profile.ts', message)
self.assertIn('run default', message)
self.assertNotIn('oil_motion_config.py set', message)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,43 @@
"""系统后端替身测试;不读取或写入真实凭据。"""
import io
import sys
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch, MagicMock
from contextlib import redirect_stdout, redirect_stderr
ROOT = Path(__file__).parents[1]
SCRIPTS = ROOT / 'scripts'
if not SCRIPTS.exists(): SCRIPTS = ROOT / 'see/scripts'
sys.path.insert(0, str(SCRIPTS))
import secure_credentials as store
class CredentialTests(unittest.TestCase):
def test_save_read_delete_only_use_backend(self):
values = {}
backend = MagicMock()
backend.get_password.side_effect = lambda service, ref: values.get(ref)
backend.set_password.side_effect = lambda service, ref, secret: values.update({ref:secret})
backend.delete_password.side_effect = lambda service, ref: values.pop(ref)
output = io.StringIO()
with patch.object(store, 'backend', return_value=backend), redirect_stdout(output), redirect_stderr(output):
store.save('test/reference', 'fake-credential-for-tests')
self.assertEqual(store.read('test/reference'), 'fake-credential-for-tests')
store.delete('test/reference')
self.assertEqual(values, {})
self.assertNotIn('fake-credential-for-tests', output.getvalue())
def test_backend_failure_never_echoes_secret(self):
backend = MagicMock()
backend.set_password.side_effect = RuntimeError('fake-credential-for-tests')
with patch.object(store, 'backend', return_value=backend):
with self.assertRaises(RuntimeError) as error:
store.save('test/reference', 'fake-credential-for-tests')
self.assertNotIn('fake-credential-for-tests', str(error.exception))
def test_unavailable_system_backend_does_not_select_plaintext(self):
with patch.object(store.importlib, 'import_module', side_effect=ImportError), self.assertRaises(RuntimeError):
store.backend()
if __name__ == '__main__': unittest.main()
+345
View File
@@ -0,0 +1,345 @@
from __future__ import annotations
import importlib.util
import json
import re
import shutil
import subprocess
import sys
import tempfile
import unittest
from argparse import Namespace
from pathlib import Path
from unittest import mock
from PIL import Image
ROOT = Path(__file__).resolve().parents[1]
SCRIPT_DIR = ROOT / "scripts"
sys.path.insert(0, str(SCRIPT_DIR))
SPEC = importlib.util.spec_from_file_location(
"video_job",
SCRIPT_DIR / "video_job.py",
)
assert SPEC and SPEC.loader
VIDEO = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = VIDEO
SPEC.loader.exec_module(VIDEO)
FFMPEG = shutil.which("ffmpeg")
# 拆开拼接,避免本测试文件自己被冲突标记扫描命中
CONFLICT_MARKER = re.compile(
r"^(?:{lt}|{gt}|{pipe}|{eq})$".format(
lt="<" * 7 + r".*",
gt=">" * 7 + r".*",
pipe=r"\|" * 7 + r".*",
eq="=" * 7,
)
)
SKIPPED_DIRS = {".git", "__pycache__", ".venv", "node_modules"}
class VideoResolutionTests(unittest.TestCase):
def test_h3_max_accepts_supported_resolutions_and_normalizes_case(self) -> None:
args = VIDEO.parser().parse_args(["--prompt", "测试", "--output", "out.mp4"])
self.assertEqual(VIDEO.build_payload(args)["resolution"], "768P")
for resolution in ("480P", "480p", "768P", "768p"):
with self.subTest(resolution=resolution):
args.resolution = resolution
self.assertEqual(
VIDEO.build_payload(args)["resolution"], resolution.upper()
)
def test_h3_max_rejects_unsupported_resolutions_before_credentials_or_submit(self) -> None:
for resolution in ("2K", "1080P", "4K", "720p"):
with self.subTest(resolution=resolution):
args = VIDEO.parser().parse_args(
["--prompt", "测试", "--resolution", resolution, "--output", "out.mp4"]
)
with (
mock.patch.object(VIDEO, "require_api_key") as credentials,
mock.patch.object(VIDEO, "request_json") as request,
self.assertRaises(ValueError) as raised,
):
VIDEO.generate(args)
self.assertIn(VIDEO.DEFAULT_MODEL, str(raised.exception))
self.assertIn(resolution, str(raised.exception))
self.assertIn("--resolution 768P", str(raised.exception))
self.assertIn("--resolution 480P", str(raised.exception))
credentials.assert_not_called()
request.assert_not_called()
def test_other_models_keep_their_native_resolution(self) -> None:
args = VIDEO.parser().parse_args(
[
"--prompt", "测试", "--output", "out.mp4",
"--model", "minimax/minimax-h3", "--resolution", "2K",
]
)
self.assertEqual(VIDEO.build_payload(args)["resolution"], "2K")
class ConflictMarkerTests(unittest.TestCase):
def test_no_git_conflict_markers_anywhere_in_skill(self) -> None:
offenders: list[str] = []
for path in sorted(ROOT.rglob("*")):
if not path.is_file():
continue
if SKIPPED_DIRS & set(path.relative_to(ROOT).parts):
continue
data = path.read_bytes()
if b"\x00" in data[:8192]:
continue
text = data.decode("utf-8", errors="ignore")
for number, line in enumerate(text.splitlines(), start=1):
if CONFLICT_MARKER.match(line):
relative = path.relative_to(ROOT)
offenders.append(f"{relative}:{number}")
self.assertEqual(
offenders,
[],
"Skill 内不得残留 Git 冲突标记:" + ", ".join(offenders),
)
def make_test_video(path: Path, frames: int = 24) -> None:
subprocess.run(
[
FFMPEG or "ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-y",
"-f",
"lavfi",
"-i",
f"testsrc=duration={frames / 12}:size=64x64:rate=12",
"-pix_fmt",
"yuv420p",
str(path),
],
check=True,
)
def decode_rgb(path: Path) -> list[tuple[int, int, int]]:
with Image.open(path) as opened:
image = opened.convert("RGB")
if hasattr(image, "get_flattened_data"):
return list(image.get_flattened_data())
return list(image.getdata())
@unittest.skipUnless(FFMPEG, "需要 ffmpeg 才能验证尾帧提取")
class LastFrameFallbackTests(unittest.TestCase):
def setUp(self) -> None:
self.directory = tempfile.TemporaryDirectory()
self.root = Path(self.directory.name)
self.video = self.root / "master.mp4"
make_test_video(self.video)
def tearDown(self) -> None:
self.directory.cleanup()
def test_extract_last_frame_returns_real_tail(self) -> None:
output = self.root / "tail.jpg"
VIDEO.extract_last_frame(self.video, output)
self.assertTrue(output.is_file())
self.assertGreater(output.stat().st_size, 0)
tail = decode_rgb(output)
reference = self.root / "reference.png"
subprocess.run(
[
FFMPEG or "ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-y",
"-sseof",
"-0.2",
"-i",
str(self.video),
"-frames:v",
"1",
str(reference),
],
check=True,
)
first = self.root / "first.png"
subprocess.run(
[
FFMPEG or "ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-y",
"-i",
str(self.video),
"-frames:v",
"1",
str(first),
],
check=True,
)
def mean_delta(a: list[tuple[int, int, int]], b: list[tuple[int, int, int]]) -> float:
return sum(
abs(pixel_a[channel] - pixel_b[channel])
for pixel_a, pixel_b in zip(a, b, strict=True)
for channel in range(3)
) / (len(a) * 3)
# 提取结果接近真实尾帧,而不是被静默替换成首帧
self.assertLess(mean_delta(tail, decode_rgb(reference)), 12)
self.assertGreater(mean_delta(tail, decode_rgb(first)), 1)
def run_generate(self, final_response: dict) -> tuple[Path, Path, dict]:
output = self.root / "out.mp4"
last_frame_output = self.root / "tail-out.jpg"
metadata = self.root / "meta.json"
args = Namespace(
prompt="测试",
prompt_file=None,
first_frame=None,
last_frame=None,
loop_frame=False,
reference_image=[],
model="minimax/minimax-h3",
resolution="768p",
ratio=None,
duration=5,
seed=None,
frames=None,
stage="pilot",
segment_index=1,
pilot_approval=None,
continuity_mode=None,
previous_tail=None,
frame_chain_manifest=None,
output=str(output),
last_frame_output=str(last_frame_output),
metadata=str(metadata),
poll_interval=0.01,
timeout=30.0,
force=False,
)
def fake_request(method: str, url: str, api_key: str, payload=None) -> dict:
if method == "POST":
return {"id": "job-1"}
return final_response
def fake_download(url: str, target: Path) -> None:
if "last-frame" in url:
target.write_bytes((self.root / "api-tail.png").read_bytes())
else:
shutil.copy2(self.video, target)
api_tail = Image.new("RGB", (64, 64), (12, 34, 56))
api_tail.save(self.root / "api-tail.png")
with (
mock.patch.object(VIDEO, "request_json", side_effect=fake_request),
mock.patch.object(VIDEO, "download", side_effect=fake_download),
mock.patch.object(VIDEO, "require_api_key", return_value="key"),
):
VIDEO.generate(args)
return output, last_frame_output, json.loads(
metadata.read_text(encoding="utf-8")
)
def test_api_last_frame_is_preferred(self) -> None:
_, last_frame_output, metadata = self.run_generate(
{
"status": "succeeded",
"video_url": "https://example.com/video.mp4",
"last_frame_url": "https://example.com/last-frame.jpg",
}
)
self.assertTrue(last_frame_output.is_file())
self.assertEqual(decode_rgb(last_frame_output)[0], (12, 34, 56))
self.assertEqual(metadata["lastFrame"]["source"], "api")
self.assertEqual(
metadata["lastFrame"]["path"], str(last_frame_output.resolve())
)
def test_missing_api_last_frame_falls_back_to_video(self) -> None:
_, last_frame_output, metadata = self.run_generate(
{
"status": "succeeded",
"video_url": "https://example.com/video.mp4",
}
)
self.assertTrue(last_frame_output.is_file())
with Image.open(last_frame_output) as image:
self.assertEqual(image.size, (64, 64))
self.assertEqual(metadata["lastFrame"]["source"], "video-fallback")
def test_default_last_frame_output_is_always_produced(self) -> None:
output = self.root / "default.mp4"
metadata = self.root / "default.meta.json"
args = Namespace(
prompt="测试",
prompt_file=None,
first_frame=None,
last_frame=None,
loop_frame=False,
reference_image=[],
model="minimax/minimax-h3",
resolution="768p",
ratio=None,
duration=5,
seed=None,
frames=None,
stage="pilot",
segment_index=1,
pilot_approval=None,
continuity_mode=None,
previous_tail=None,
frame_chain_manifest=None,
output=str(output),
last_frame_output=None,
metadata=str(metadata),
poll_interval=0.01,
timeout=30.0,
force=False,
)
with (
mock.patch.object(
VIDEO,
"request_json",
side_effect=[
{"id": "job-1"},
{
"status": "succeeded",
"video_url": "https://example.com/video.mp4",
},
],
),
mock.patch.object(
VIDEO,
"download",
side_effect=lambda url, target: shutil.copy2(self.video, target),
),
mock.patch.object(VIDEO, "require_api_key", return_value="key"),
):
VIDEO.generate(args)
default_tail = self.root / "default-last-frame.jpg"
self.assertTrue(default_tail.is_file())
payload = json.loads(metadata.read_text(encoding="utf-8"))
self.assertEqual(payload["lastFrame"]["source"], "video-fallback")
self.assertEqual(
payload["lastFrame"]["path"], str(default_tail.resolve())
)
if __name__ == "__main__":
unittest.main()