内容分四块: 1、产品规划产出 —— MCN 短视频整合营销工作台的①段五份(1a 需求/1b 竞品/1c 画像/1d 策略/1e 场景)、②段两份(2a 功能/2b 布局)、③段界面(DESIGN.md 契约与令牌表 + mcn-workbench.html 原型 + 实测/会诊/审查三份 + 23 张闸门截图)。 2、开源竞品调研 —— 5 个内容工作台项目的取证原始件与 1b 系列分析文档。 3、参考资料 —— 竞品视频抽帧 1145 张 + 2 个源视频 + 功能点截图。 4、机制侧 —— 协作脚本与状态台账、工作区记忆日志、抽帧/OCR 脚本。 .gitignore 只排运行时日志、脚本备份副本与一次性探针输出,其余按原样入库。
140 lines
4.7 KiB
Python
140 lines
4.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""OpenCreator 文件树统计(可复算)
|
||
输入:取证/opencreator/tree_*.json(本棒分片拉取)+ 取证/api/opencreator.tree.top.json(第 1 棒顶层)
|
||
输出:取证/opencreator/opencreator.tree.stat.json + 控制台摘要
|
||
口径:只数 type=='blob';顶层目录取 path 第一段。
|
||
"""
|
||
import json, os, collections
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
OC = os.path.join(HERE, "opencreator")
|
||
API = os.path.join(HERE, "api")
|
||
|
||
# 切片树里的 path 是相对「该切片根」的,必须补回前缀才是仓根相对路径
|
||
SLICES = {
|
||
"tree_.github.json": ".github",
|
||
"tree_apps.json": "apps",
|
||
"tree_assets.json": "assets",
|
||
"tree_packages.json": "packages",
|
||
"tree_resources.json": "resources",
|
||
"tree_runtime.json": "runtime",
|
||
"tree_scripts.json": "scripts",
|
||
"tree_skills.json": "skills",
|
||
"tree_template.json": "template",
|
||
"tree_docs.json": "docs",
|
||
}
|
||
|
||
blobs = [] # (path, size)
|
||
trees = 0
|
||
for f, prefix in SLICES.items():
|
||
d = json.load(open(os.path.join(OC, f), encoding="utf-8"))
|
||
assert not d.get("truncated"), f + " truncated"
|
||
for e in d["tree"]:
|
||
if e["type"] == "blob":
|
||
blobs.append((prefix + "/" + e["path"], e.get("size", 0)))
|
||
else:
|
||
trees += 1
|
||
|
||
root = json.load(open(os.path.join(API, "opencreator.tree.top.json"), encoding="utf-8"))
|
||
for e in root["tree"]:
|
||
if e["type"] == "blob":
|
||
blobs.append((e["path"], e.get("size", 0)))
|
||
else:
|
||
trees += 1
|
||
|
||
# 去重(docs/specs 在 docs 整树里已含)
|
||
seen, uniq = set(), []
|
||
for p, s in blobs:
|
||
if p in seen:
|
||
continue
|
||
seen.add(p)
|
||
uniq.append((p, s))
|
||
blobs = uniq
|
||
|
||
by_top = collections.Counter()
|
||
for p, _ in blobs:
|
||
by_top[p.split("/")[0]] += 1
|
||
|
||
def sub(prefix, n=2):
|
||
c = collections.Counter()
|
||
for p, _ in blobs:
|
||
if p.startswith(prefix + "/"):
|
||
parts = p.split("/")
|
||
c["/".join(parts[:n])] += 1
|
||
return c
|
||
|
||
ext = collections.Counter()
|
||
for p, _ in blobs:
|
||
ext[os.path.splitext(p)[1].lower() or "(noext)"] += 1
|
||
|
||
apps = sub("apps", 3)
|
||
daemon_src = collections.Counter()
|
||
for p, _ in blobs:
|
||
if p.startswith("apps/daemon/src/"):
|
||
parts = p.split("/")
|
||
daemon_src["/".join(parts[:4]) if len(parts) > 3 else "/".join(parts[:3])] += 1
|
||
web_src = collections.Counter()
|
||
for p, _ in blobs:
|
||
if p.startswith("apps/web/src/"):
|
||
parts = p.split("/")
|
||
web_src["/".join(parts[:4]) if len(parts) > 3 else "/".join(parts[:3])] += 1
|
||
rt = sub("runtime/krillinai", 3)
|
||
tpl = collections.Counter()
|
||
for p, _ in blobs:
|
||
if p.startswith("template/"):
|
||
parts = p.split("/")
|
||
tpl[parts[1]] += 1
|
||
# 模板条目数(二级目录)
|
||
tpl_entries = collections.Counter()
|
||
for p, _ in blobs:
|
||
if p.startswith("template/") and len(p.split("/")) >= 3:
|
||
tpl_entries["/".join(p.split("/")[:2])] += 1
|
||
# writing-templates 模板条目
|
||
wt = collections.Counter()
|
||
for p, _ in blobs:
|
||
if p.startswith("packages/writing-templates/templates/"):
|
||
wt["/".join(p.split("/")[:4])] += 1
|
||
|
||
test_files = [p for p, _ in blobs if ".test." in p or ".spec." in p]
|
||
|
||
out = {
|
||
"repo": "krillinai/OpenCreator",
|
||
"branch": "master",
|
||
"total_blobs": len(blobs),
|
||
"total_trees": trees,
|
||
"total_bytes": sum(s for _, s in blobs),
|
||
"blobs_by_top_dir": dict(by_top.most_common()),
|
||
"ext_top20": dict(ext.most_common(20)),
|
||
"apps_by_3": dict(apps.most_common()),
|
||
"apps_daemon_src": dict(daemon_src.most_common()),
|
||
"apps_web_src": dict(web_src.most_common()),
|
||
"runtime_krillinai": dict(rt.most_common()),
|
||
"template_by_category": dict(tpl.most_common()),
|
||
"template_entries_by_category": {k: sum(1 for p, _ in blobs if p.startswith(k + "/"))
|
||
for k in tpl},
|
||
"writing_templates_entries": dict(wt.most_common()),
|
||
"test_file_count": len(test_files),
|
||
"test_files_sample": test_files[:15],
|
||
}
|
||
with open(os.path.join(OC, "opencreator.tree.stat.json"), "w", encoding="utf-8") as f:
|
||
json.dump(out, f, ensure_ascii=False, indent=1)
|
||
|
||
print("total_blobs=%d total_trees=%d total_bytes=%d test_files=%d"
|
||
% (len(blobs), trees, out["total_bytes"], len(test_files)))
|
||
print("顶层目录 blob 数:", dict(by_top.most_common()))
|
||
print()
|
||
print("apps 三层:", dict(apps.most_common(12)))
|
||
print()
|
||
print("daemon/src 四层:", dict(daemon_src.most_common(15)))
|
||
print()
|
||
print("web/src 四层:", dict(web_src.most_common(15)))
|
||
print()
|
||
print("runtime/krillinai 三层:", dict(rt.most_common(20)))
|
||
print()
|
||
print("template 分类 blob 数:", dict(tpl.most_common()))
|
||
print("template 条目数(二级目录):", dict(out["template_entries_by_category"]))
|
||
print()
|
||
print("writing-templates 条目:", dict(wt.most_common()))
|
||
print()
|
||
print("扩展名 top20:", dict(ext.most_common(20)))
|