#!/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