一、问题(本轮实测)
`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` 挪回原处即可。
423 lines
15 KiB
Python
423 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
"""为 Pilot 批准与多段视频首尾帧链提供可审计的硬门。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import hashlib
|
||
import json
|
||
from datetime import datetime, timezone
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
import numpy as np
|
||
from PIL import Image
|
||
import yaml
|
||
|
||
from media_edges import extract_first_frame, extract_last_frame
|
||
|
||
PILOT_ARTIFACTS = (
|
||
"conceptContract",
|
||
"firstFrame",
|
||
"lastFrame",
|
||
"pilotVideo",
|
||
"pageEvidence",
|
||
)
|
||
|
||
class UniqueKeyLoader(yaml.SafeLoader):
|
||
"""拒绝 YAML 重复键,避免后值静默覆盖已经批准的合同语义。"""
|
||
|
||
|
||
def construct_unique_mapping(
|
||
loader: UniqueKeyLoader,
|
||
node: yaml.nodes.MappingNode,
|
||
deep: bool = False,
|
||
) -> dict[Any, Any]:
|
||
loader.flatten_mapping(node)
|
||
mapping: dict[Any, Any] = {}
|
||
for key_node, value_node in node.value:
|
||
key = loader.construct_object(key_node, deep=deep)
|
||
if key in mapping:
|
||
raise ValueError(f"Concept Contract 包含重复字段:{key}")
|
||
mapping[key] = loader.construct_object(value_node, deep=deep)
|
||
return mapping
|
||
|
||
|
||
UniqueKeyLoader.add_constructor(
|
||
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
||
construct_unique_mapping,
|
||
)
|
||
|
||
|
||
def sha256_file(path: Path) -> str:
|
||
digest = hashlib.sha256()
|
||
with path.open("rb") as source:
|
||
for chunk in iter(lambda: source.read(1024 * 1024), b""):
|
||
digest.update(chunk)
|
||
return digest.hexdigest()
|
||
|
||
|
||
def artifact_record(raw_path: str | Path) -> dict[str, Any]:
|
||
path = Path(raw_path).expanduser().resolve()
|
||
if not path.is_file():
|
||
raise FileNotFoundError(f"验收工件不存在:{path}")
|
||
return {
|
||
"path": str(path),
|
||
"bytes": path.stat().st_size,
|
||
"sha256": sha256_file(path),
|
||
}
|
||
|
||
|
||
def validate_artifact(
|
||
record: dict[str, Any],
|
||
label: str,
|
||
require_exact_hash: bool = True,
|
||
force: bool = False,
|
||
) -> None:
|
||
path = Path(str(record.get("path", ""))).expanduser().resolve()
|
||
if not path.is_file():
|
||
raise FileNotFoundError(f"{label} 工件不存在:{path}")
|
||
if path.stat().st_size == 0:
|
||
raise ValueError(f"{label} 工件为空文件:{path}")
|
||
if require_exact_hash:
|
||
actual_size = path.stat().st_size
|
||
actual_hash = sha256_file(path)
|
||
if actual_size != record.get("bytes") or actual_hash != record.get("sha256"):
|
||
if force:
|
||
print(
|
||
f"警告:{label} 工件哈希已变化,已通过 --force 忽略差异:{path}",
|
||
file=sys.stderr,
|
||
)
|
||
else:
|
||
raise ValueError(f"{label} 工件已变化,Pilot 批准失效:{path}")
|
||
|
||
|
||
def contract_continuity_mode(raw_path: str | Path) -> str:
|
||
path = Path(raw_path).expanduser().resolve()
|
||
if not path.is_file():
|
||
raise FileNotFoundError(f"Concept Contract 不存在:{path}")
|
||
try:
|
||
contract = yaml.load(
|
||
path.read_text(encoding="utf-8"),
|
||
Loader=UniqueKeyLoader,
|
||
)
|
||
except yaml.YAMLError as error:
|
||
raise ValueError(f"Concept Contract YAML 无效:{error}") from error
|
||
if not isinstance(contract, dict):
|
||
raise ValueError("Concept Contract 顶层必须是 YAML 对象")
|
||
mode = contract.get("clip_continuity")
|
||
if mode not in {"chain", "independent"}:
|
||
raise ValueError(
|
||
"Concept Contract 顶层必须显式包含 "
|
||
"clip_continuity: chain | independent"
|
||
)
|
||
return mode
|
||
|
||
|
||
def create_pilot_approval(args: argparse.Namespace) -> dict[str, Any]:
|
||
if args.decision != "pass":
|
||
raise ValueError("只有明确传入 --decision pass 才能生成 Pilot 批准文件")
|
||
continuity_mode = contract_continuity_mode(args.contract)
|
||
artifacts = {
|
||
"conceptContract": artifact_record(args.contract),
|
||
"firstFrame": artifact_record(args.first_frame),
|
||
"lastFrame": artifact_record(args.last_frame),
|
||
"pilotVideo": artifact_record(args.video),
|
||
"pageEvidence": artifact_record(args.page_evidence),
|
||
}
|
||
if args.identity_bible:
|
||
artifacts["identityBible"] = artifact_record(args.identity_bible)
|
||
report = {
|
||
"schemaVersion": 1,
|
||
"passed": True,
|
||
"approvedAt": datetime.now(timezone.utc).isoformat(),
|
||
"reviewer": args.reviewer,
|
||
"continuityMode": continuity_mode,
|
||
"notes": args.notes or "",
|
||
"artifacts": artifacts,
|
||
}
|
||
output = Path(args.output).expanduser().resolve()
|
||
if output.exists() and not args.force:
|
||
raise FileExistsError(f"Pilot 批准文件已存在:{output};确认后使用 --force")
|
||
output.parent.mkdir(parents=True, exist_ok=True)
|
||
output.write_text(
|
||
json.dumps(report, ensure_ascii=False, indent=2),
|
||
encoding="utf-8",
|
||
)
|
||
return report
|
||
|
||
|
||
def validate_pilot_approval(
|
||
raw_path: str | Path,
|
||
force: bool = False,
|
||
) -> dict[str, Any]:
|
||
path = Path(raw_path).expanduser().resolve()
|
||
if not path.is_file():
|
||
raise FileNotFoundError(f"缺少 Pilot 批准文件:{path}")
|
||
report = json.loads(path.read_text(encoding="utf-8"))
|
||
if report.get("schemaVersion") != 1 or report.get("passed") is not True:
|
||
raise ValueError(f"Pilot 尚未通过:{path}")
|
||
if not str(report.get("reviewer", "")).strip():
|
||
raise ValueError(f"Pilot 批准缺少 reviewer:{path}")
|
||
artifacts = report.get("artifacts")
|
||
if not isinstance(artifacts, dict):
|
||
raise ValueError(f"Pilot 批准缺少 artifacts:{path}")
|
||
for label in PILOT_ARTIFACTS:
|
||
record = artifacts.get(label)
|
||
if not isinstance(record, dict):
|
||
raise ValueError(f"Pilot 批准缺少 {label}:{path}")
|
||
is_strict = label != "pageEvidence"
|
||
validate_artifact(record, label, require_exact_hash=is_strict, force=force)
|
||
approved_mode = report.get("continuityMode")
|
||
if approved_mode not in {"chain", "independent"}:
|
||
raise ValueError(f"Pilot 批准缺少有效 continuityMode:{path}")
|
||
contract_path = artifacts["conceptContract"]["path"]
|
||
actual_mode = contract_continuity_mode(contract_path)
|
||
if actual_mode != approved_mode:
|
||
if force:
|
||
print(
|
||
f"警告:Concept Contract 连续模式已由 {approved_mode} 变为 {actual_mode},已通过 --force 允许执行",
|
||
file=sys.stderr,
|
||
)
|
||
else:
|
||
raise ValueError(f"Concept Contract 连续模式已变化,Pilot 批准失效:{path}")
|
||
identity = artifacts.get("identityBible")
|
||
if identity is not None:
|
||
if not isinstance(identity, dict):
|
||
raise ValueError(f"Pilot identityBible 记录无效:{path}")
|
||
validate_artifact(identity, "identityBible", force=force)
|
||
return report
|
||
|
||
|
||
def verify_frame_chain(
|
||
previous_tail: str | Path,
|
||
next_first: str | Path,
|
||
segment_index: int,
|
||
manifest_path: str | Path | None = None,
|
||
force: bool = False,
|
||
) -> dict[str, Any]:
|
||
if segment_index < 2:
|
||
raise ValueError("帧链只用于第 2 段及之后的片段")
|
||
previous = artifact_record(previous_tail)
|
||
current = artifact_record(next_first)
|
||
exact_match = (
|
||
previous["bytes"] == current["bytes"]
|
||
and previous["sha256"] == current["sha256"]
|
||
)
|
||
if not exact_match:
|
||
if force:
|
||
print(
|
||
f"警告:第 {segment_index} 段首帧与上一段尾帧哈希不一致,已通过 --force 忽略差异继续执行",
|
||
file=sys.stderr,
|
||
)
|
||
else:
|
||
raise ValueError(
|
||
"连续帧链断裂:下一段 --first-frame 不是上一段验收尾帧的原文件"
|
||
)
|
||
link = {
|
||
"segmentIndex": segment_index,
|
||
"verifiedAt": datetime.now(timezone.utc).isoformat(),
|
||
"exactSha256Match": exact_match,
|
||
"previousTail": previous,
|
||
"nextFirst": current,
|
||
}
|
||
if manifest_path is not None:
|
||
manifest = Path(manifest_path).expanduser().resolve()
|
||
if manifest.is_file():
|
||
payload = json.loads(manifest.read_text(encoding="utf-8"))
|
||
else:
|
||
payload = {"schemaVersion": 1, "links": []}
|
||
links = payload.setdefault("links", [])
|
||
if not isinstance(links, list):
|
||
raise ValueError(f"帧链清单格式错误:{manifest}")
|
||
links = [
|
||
item
|
||
for item in links
|
||
if item.get("segmentIndex") != segment_index
|
||
]
|
||
links.append(link)
|
||
payload["links"] = sorted(links, key=lambda item: item["segmentIndex"])
|
||
manifest.parent.mkdir(parents=True, exist_ok=True)
|
||
manifest.write_text(
|
||
json.dumps(payload, ensure_ascii=False, indent=2),
|
||
encoding="utf-8",
|
||
)
|
||
return link
|
||
|
||
|
||
def frame_similarity(previous: Path, current: Path) -> dict[str, float]:
|
||
with Image.open(previous) as opened:
|
||
first = np.asarray(opened.convert("RGB"), dtype=np.float64) / 255.0
|
||
with Image.open(current) as opened:
|
||
second = np.asarray(opened.convert("RGB"), dtype=np.float64) / 255.0
|
||
if first.shape != second.shape:
|
||
raise ValueError(
|
||
f"相邻成片尺寸不同:{first.shape[1]}x{first.shape[0]} 与 "
|
||
f"{second.shape[1]}x{second.shape[0]}"
|
||
)
|
||
mae = float(np.mean(np.abs(first - second)))
|
||
first_mean = np.mean(first, axis=(0, 1))
|
||
second_mean = np.mean(second, axis=(0, 1))
|
||
first_variance = np.var(first, axis=(0, 1))
|
||
second_variance = np.var(second, axis=(0, 1))
|
||
covariance = np.mean(
|
||
(first - first_mean) * (second - second_mean), axis=(0, 1)
|
||
)
|
||
c1 = 0.01**2
|
||
c2 = 0.03**2
|
||
ssim = np.mean(
|
||
((2 * first_mean * second_mean + c1) * (2 * covariance + c2))
|
||
/ (
|
||
(first_mean**2 + second_mean**2 + c1)
|
||
* (first_variance + second_variance + c2)
|
||
)
|
||
)
|
||
return {"ssim": round(float(ssim), 6), "normalizedMae": round(mae, 6)}
|
||
|
||
|
||
def verify_output_chain(
|
||
previous_video: str | Path,
|
||
next_video: str | Path,
|
||
segment_index: int,
|
||
manifest_path: str | Path,
|
||
evidence_directory: str | Path,
|
||
minimum_ssim: float = 0.97,
|
||
maximum_mae: float = 0.04,
|
||
) -> dict[str, Any]:
|
||
if segment_index < 2:
|
||
raise ValueError("成片接缝只用于第 2 段及之后的片段")
|
||
if not 0 <= minimum_ssim <= 1 or not 0 <= maximum_mae <= 1:
|
||
raise ValueError("SSIM 与 MAE 阈值必须在 0..1")
|
||
evidence = Path(evidence_directory).expanduser().resolve()
|
||
previous_tail = evidence / f"segment-{segment_index - 1:02d}-tail.png"
|
||
next_head = evidence / f"segment-{segment_index:02d}-head.png"
|
||
extract_last_frame(previous_video, previous_tail)
|
||
extract_first_frame(next_video, next_head)
|
||
metrics = frame_similarity(previous_tail, next_head)
|
||
passed = (
|
||
metrics["ssim"] >= minimum_ssim
|
||
and metrics["normalizedMae"] <= maximum_mae
|
||
)
|
||
link = {
|
||
"segmentIndex": segment_index,
|
||
"verifiedAt": datetime.now(timezone.utc).isoformat(),
|
||
"passed": passed,
|
||
"thresholds": {
|
||
"minimumSsim": minimum_ssim,
|
||
"maximumNormalizedMae": maximum_mae,
|
||
},
|
||
"metrics": metrics,
|
||
"previousTail": artifact_record(previous_tail),
|
||
"nextHead": artifact_record(next_head),
|
||
"previousVideo": artifact_record(previous_video),
|
||
"nextVideo": artifact_record(next_video),
|
||
}
|
||
manifest = Path(manifest_path).expanduser().resolve()
|
||
if manifest.is_file():
|
||
payload = json.loads(manifest.read_text(encoding="utf-8"))
|
||
else:
|
||
payload = {"schemaVersion": 1, "links": [], "outputLinks": []}
|
||
output_links = payload.setdefault("outputLinks", [])
|
||
if not isinstance(output_links, list):
|
||
raise ValueError(f"帧链清单 outputLinks 格式错误:{manifest}")
|
||
output_links = [
|
||
item for item in output_links if item.get("segmentIndex") != segment_index
|
||
]
|
||
output_links.append(link)
|
||
payload["outputLinks"] = sorted(
|
||
output_links, key=lambda item: item["segmentIndex"]
|
||
)
|
||
manifest.parent.mkdir(parents=True, exist_ok=True)
|
||
manifest.write_text(
|
||
json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
|
||
)
|
||
if not passed:
|
||
raise ValueError(
|
||
"相邻成片接缝不连续:"
|
||
f"SSIM {metrics['ssim']:.4f},MAE {metrics['normalizedMae']:.4f}"
|
||
)
|
||
return link
|
||
|
||
|
||
def parser() -> argparse.ArgumentParser:
|
||
result = argparse.ArgumentParser(description="校验 oil-motion 生产硬门")
|
||
commands = result.add_subparsers(dest="command", required=True)
|
||
|
||
approve = commands.add_parser("approve-pilot", help="生成可校验的 Pilot 批准文件")
|
||
approve.add_argument("--contract", required=True)
|
||
approve.add_argument("--identity-bible")
|
||
approve.add_argument("--first-frame", required=True)
|
||
approve.add_argument("--last-frame", required=True)
|
||
approve.add_argument("--video", required=True)
|
||
approve.add_argument("--page-evidence", required=True)
|
||
approve.add_argument("--reviewer", required=True)
|
||
approve.add_argument("--notes")
|
||
approve.add_argument("--decision", choices=("pass", "reject"), required=True)
|
||
approve.add_argument("--output", required=True)
|
||
approve.add_argument("--force", action="store_true")
|
||
|
||
check = commands.add_parser("check-pilot", help="复核 Pilot 工件未变化")
|
||
check.add_argument("approval")
|
||
|
||
chain = commands.add_parser("verify-chain", help="验证并记录相邻片段帧链")
|
||
chain.add_argument("--previous-tail", required=True)
|
||
chain.add_argument("--next-first", required=True)
|
||
chain.add_argument("--segment-index", type=int, required=True)
|
||
chain.add_argument("--manifest", required=True)
|
||
|
||
output_chain = commands.add_parser(
|
||
"verify-output-chain", help="验证相邻成片解码后的实际接缝"
|
||
)
|
||
output_chain.add_argument("--previous-video", required=True)
|
||
output_chain.add_argument("--next-video", required=True)
|
||
output_chain.add_argument("--segment-index", type=int, required=True)
|
||
output_chain.add_argument("--manifest", required=True)
|
||
output_chain.add_argument("--evidence-dir", required=True)
|
||
output_chain.add_argument("--min-ssim", type=float, default=0.97)
|
||
output_chain.add_argument("--max-mae", type=float, default=0.04)
|
||
return result
|
||
|
||
|
||
def main() -> int:
|
||
args = parser().parse_args()
|
||
if args.command == "approve-pilot":
|
||
create_pilot_approval(args)
|
||
print(f"Pilot 已批准:{Path(args.output).expanduser().resolve()}")
|
||
elif args.command == "check-pilot":
|
||
validate_pilot_approval(args.approval)
|
||
print(f"Pilot 工件有效:{Path(args.approval).expanduser().resolve()}")
|
||
elif args.command == "verify-chain":
|
||
verify_frame_chain(
|
||
args.previous_tail,
|
||
args.next_first,
|
||
args.segment_index,
|
||
args.manifest,
|
||
)
|
||
print(f"帧链已验证:segment {args.segment_index}")
|
||
else:
|
||
verify_output_chain(
|
||
args.previous_video,
|
||
args.next_video,
|
||
args.segment_index,
|
||
args.manifest,
|
||
args.evidence_dir,
|
||
args.min_ssim,
|
||
args.max_mae,
|
||
)
|
||
print(f"成片接缝已验证:segment {args.segment_index}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
raise SystemExit(main())
|
||
except (
|
||
FileNotFoundError,
|
||
FileExistsError,
|
||
RuntimeError,
|
||
ValueError,
|
||
json.JSONDecodeError,
|
||
) as error:
|
||
print(f"错误:{error}")
|
||
raise SystemExit(1) from error
|