File size: 12,256 Bytes
5b93402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7a715a
5b93402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7a715a
 
 
 
 
 
 
 
5b93402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7a715a
 
 
5b93402
 
 
 
 
 
a7a715a
5b93402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#!/usr/bin/env python3
"""Build a public-safe final video publishing kit for Devpost submission."""

from __future__ import annotations

import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
SCHEMA = "proofframe.final_video_publish_kit.v1"
DEFAULT_JSON = ROOT / "docs" / "assets" / "final-video-publish-kit.json"
DEFAULT_MD = ROOT / "docs" / "assets" / "final-video-publish-kit.md"
PUBLIC_DEMO_URL = "https://adjcjh-backblaze-proofframe.hf.space/?judge=1"
REPOSITORY_URL = "https://github.com/adjcjh777/backblaze-proofframe"
ALLOWED_HOSTS = ["YouTube", "Vimeo", "Youku"]


def utc_now() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def load_json(path: Path) -> dict[str, Any]:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except (FileNotFoundError, json.JSONDecodeError):
        return {}
    return data if isinstance(data, dict) else {}


def source_summary(root: Path, relative_path: str, schema: str) -> dict[str, Any]:
    data = load_json(root / relative_path)
    return {
        "path": relative_path,
        "present": bool(data),
        "schema": data.get("schema"),
        "schema_ok": data.get("schema") == schema,
        "mode": data.get("mode"),
        "ok": data.get("ok"),
        "safe_to_submit": data.get("safe_to_submit"),
    }


def build_chapters(storyboard: dict[str, Any]) -> list[dict[str, Any]]:
    shots = storyboard.get("shots")
    if not isinstance(shots, list) or not shots:
        shots = [
            {"id": "opening", "title": "ProofFrame judge-mode slate", "seconds": 20},
            {"id": "workflow", "title": "Generate and review a provenance packet", "seconds": 55},
            {"id": "evidence", "title": "Manifest, checksum, storage route, and evidence ZIP", "seconds": 35},
            {"id": "gates", "title": "Final B2, Genblaze, video, and audit gates", "seconds": 25},
        ]
    chapters: list[dict[str, Any]] = []
    cursor = 0
    for shot in shots:
        if not isinstance(shot, dict):
            continue
        seconds = int(shot.get("seconds") or shot.get("duration_seconds") or 0)
        title = str(shot.get("title") or shot.get("screen") or shot.get("id") or "ProofFrame section")
        chapters.append(
            {
                "timecode": f"{cursor // 60}:{cursor % 60:02d}",
                "title": title,
                "duration_seconds": seconds,
            }
        )
        cursor += max(0, seconds)
    return chapters


def description_text(*, public_demo_url: str, repository_url: str, claim_boundary: str) -> str:
    return "\n".join(
        [
            "ProofFrame is a provenance-first media operations desk for generated campaign assets.",
            "",
            "This demo walks through one creator workflow: open the judge-mode demo, create a media packet, inspect prompt/provider/model/storage/checksum metadata, approve an asset, and download the evidence ZIP.",
            "",
            f"Public demo: {public_demo_url}",
            f"Repository: {repository_url}",
            "",
            "What to look for:",
            "- Generated media is reviewed as an evidence packet, not a loose image.",
            "- Prompt, provider/model metadata, storage pointer, checksum, approval state, and risk note stay together.",
            "- Optional Backblaze B2 and Genblaze paths are implemented and fail closed until live proof is captured.",
            "",
            f"Claim boundary: {claim_boundary}",
        ]
    )


def build_upload_checklist(public_video_check: dict[str, Any]) -> list[dict[str, Any]]:
    url_analysis = public_video_check.get("url_analysis") or {}
    return [
        {
            "id": "host_family",
            "label": "Upload to an official public video host",
            "ok": bool(url_analysis.get("official_host")),
            "detail": "Allowed host families: YouTube, Vimeo, or Youku.",
        },
        {
            "id": "public_visibility",
            "label": "Use public or unlisted visibility that Devpost judges can access",
            "ok": bool(public_video_check.get("safe_to_submit")),
            "detail": "The final URL must be reachable without login, cookies, tokens, or signed query parameters.",
        },
        {
            "id": "duration",
            "label": "Keep the final video under the event time limit",
            "ok": True,
            "detail": "Use the generated storyboard and chapter plan; rerun public_video_check after upload.",
        },
        {
            "id": "devpost_field",
            "label": "Paste the final video URL into Devpost",
            "ok": bool(public_video_check.get("safe_to_submit")),
            "detail": "After upload, set PROOFFRAME_PUBLIC_VIDEO_URL and regenerate final reports.",
        },
    ]


def source_is_final_ready(source: dict[str, Any]) -> bool:
    return bool(source["schema_ok"] and source.get("ok") is True and source.get("safe_to_submit") is True)


def final_control_is_ready(source: dict[str, Any]) -> bool:
    return bool(source["schema_ok"] and source.get("ok") is True and source.get("safe_to_submit") is True)


def build_kit(root: Path = ROOT) -> dict[str, Any]:
    root = root.resolve()
    packet = load_json(root / "docs" / "assets" / "devpost-submission-packet.json")
    storyboard = load_json(root / "docs" / "assets" / "demo-storyboard.json")
    draft = load_json(root / "docs" / "assets" / "demo-video-draft.json")
    public_video_check = load_json(root / "docs" / "assets" / "public-video-check.json")
    evidence_index = load_json(root / "docs" / "assets" / "judge-evidence-index.json")
    title = "ProofFrame: Provenance-first generated media vault"
    claim_boundary = (
        "Use this title and description for final upload only after live B2 and Genblaze proof are recorded; "
        "until then, the public demo remains local/mock and safe_to_submit=false."
    )
    sources = {
        "storyboard": source_summary(root, "docs/assets/demo-storyboard.json", "proofframe.demo_storyboard.v1"),
        "draft_video": source_summary(root, "docs/assets/demo-video-draft.json", "proofframe.demo_video_draft.v1"),
        "public_video_check": source_summary(root, "docs/assets/public-video-check.json", "proofframe.public_video_check.v1"),
        "final_control": source_summary(root, "docs/assets/final-submission-control.json", "proofframe.final_submission_control.v1"),
        "evidence_index": source_summary(root, "docs/assets/judge-evidence-index.json", "proofframe.judge_evidence_index.v1"),
    }
    ok = all(source["schema_ok"] for source in sources.values()) and bool(evidence_index.get("ok"))
    video_ready = source_is_final_ready(sources["public_video_check"])
    control_ready = final_control_is_ready(sources["final_control"])
    safe_to_submit = bool(ok and video_ready and control_ready)
    return {
        "schema": SCHEMA,
        "created_at": utc_now(),
        "ok": ok,
        "mode": "public_video_ready" if video_ready else "ready_for_final_upload",
        "safe_to_share": ok,
        "safe_to_submit": safe_to_submit,
        "final_video_ready": video_ready,
        "allowed_hosts": ALLOWED_HOSTS,
        "video_url": public_video_check.get("video_url") or packet.get("video_url"),
        "title": title,
        "description": description_text(
            public_demo_url=packet.get("demo_url") or PUBLIC_DEMO_URL,
            repository_url=packet.get("repository_url") or REPOSITORY_URL,
            claim_boundary=claim_boundary,
        ),
        "chapters": build_chapters(storyboard),
        "devpost_field": {
            "field_id": "video_url",
            "value": public_video_check.get("video_url") if video_ready else "TBD after final upload.",
            "ready": video_ready,
            "source": "docs/assets/public-video-check.json",
        },
        "upload_checklist": build_upload_checklist(public_video_check),
        "source_reports": sources,
        "draft_video": {
            "path": draft.get("video_path"),
            "public_url": draft.get("public_video_draft_url"),
            "safe_to_submit": draft.get("safe_to_submit"),
            "final_video_ready": draft.get("final_video_ready"),
        },
        "evidence_index_url": (
            "https://huggingface.co/spaces/ADJCJH/backblaze-proofframe/raw/main/"
            "docs/assets/judge-evidence-index.md"
        ),
        "claim_boundary": claim_boundary,
        "next_actions": [
            "Record the final narrated video after live B2 and Genblaze proof is visible.",
            "Upload to YouTube, Vimeo, or Youku with public or unlisted judge-accessible visibility.",
            "Set PROOFFRAME_PUBLIC_VIDEO_URL to the final URL and run scripts/public_video_check.py --verify-url --strict-final.",
            "Regenerate Devpost form kit, final control, submission audit, preview, and bundle before submit.",
        ],
    }


def render_markdown(kit: dict[str, Any]) -> str:
    lines = [
        "# ProofFrame Final Video Publish Kit",
        "",
        f"Mode: `{kit['mode']}`",
        f"OK: `{str(kit['ok']).lower()}`",
        f"Safe to submit: `{str(kit['safe_to_submit']).lower()}`",
        f"Final video ready: `{str(kit['final_video_ready']).lower()}`",
        f"Created: `{kit['created_at']}`",
        "",
        "## Upload Copy",
        "",
        f"Title: {kit['title']}",
        "",
        "Description:",
        "",
        "```text",
        kit["description"],
        "```",
        "",
        "## Chapters",
        "",
    ]
    lines.extend(
        f"- `{chapter['timecode']}` {chapter['title']} ({chapter['duration_seconds']}s)"
        for chapter in kit["chapters"]
    )
    lines.extend(["", "## Upload Checklist", ""])
    for item in kit["upload_checklist"]:
        status = "OK" if item["ok"] else "PENDING"
        lines.append(f"- {status} `{item['id']}`: {item['label']} - {item['detail']}")
    lines.extend(
        [
            "",
            "## Devpost Field",
            "",
            f"- Field: `{kit['devpost_field']['field_id']}`",
            f"- Ready: `{str(kit['devpost_field']['ready']).lower()}`",
            f"- Value: `{kit['devpost_field']['value']}`",
            "",
            "## Claim Boundary",
            "",
            kit["claim_boundary"],
            "",
            "## Next Actions",
            "",
        ]
    )
    lines.extend(f"- {action}" for action in kit["next_actions"])
    lines.append("")
    return "\n".join(lines)


def write_outputs(kit: dict[str, Any], json_path: Path, markdown_path: Path) -> None:
    json_path.parent.mkdir(parents=True, exist_ok=True)
    markdown_path.parent.mkdir(parents=True, exist_ok=True)
    json_path.write_text(json.dumps(kit, indent=2) + "\n", encoding="utf-8")
    markdown_path.write_text(render_markdown(kit), encoding="utf-8")


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Build the ProofFrame final video publishing kit.")
    parser.add_argument("--root", type=Path, default=ROOT)
    parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON)
    parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MD)
    parser.add_argument("--strict-final", action="store_true", help="Fail unless the final video URL is ready.")
    return parser


def main() -> None:
    args = build_parser().parse_args()
    kit = build_kit(args.root)
    write_outputs(kit, args.json_out, args.markdown_out)
    print(
        json.dumps(
            {
                "ok": kit["safe_to_submit"] if args.strict_final else kit["ok"],
                "mode": kit["mode"],
                "safe_to_submit": kit["safe_to_submit"],
                "final_video_ready": kit["final_video_ready"],
                "json": str(args.json_out),
                "markdown": str(args.markdown_out),
            },
            indent=2,
        )
    )
    if args.strict_final and not kit["safe_to_submit"]:
        raise SystemExit(2)
    if not kit["ok"]:
        raise SystemExit(1)


if __name__ == "__main__":
    main()