publish.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. #!/usr/bin/env python3
  2. """publish.py — 上传报告整套产物到 S3 个人空间, 生成公开URL.
  3. 用法:
  4. python3 publish.py --workdir /tmp/lapian
  5. 路径规范(硬性, 全英文小写):
  6. user/<userid>/report/lapian/<平台>/<YYYYMMDD>/
  7. report.html · video.mp4 · audio.wav · transcript.json
  8. frames/f0001.jpg... · assets/(analysis.json/comments.json/meta.json)
  9. 报告内资源引用全部相对路径 → 整个前缀自包含, 换域名也能用。
  10. 返回: report.html 的公开URL(并对每个产物做 HTTP 校验)。
  11. """
  12. import argparse
  13. import datetime
  14. import json
  15. import os
  16. import sys
  17. import urllib.request
  18. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
  19. from common import get_userid, public_url, s3_put_file # noqa: E402
  20. MANIFEST_CT = {
  21. ".html": "text/html; charset=utf-8",
  22. ".json": "application/json; charset=utf-8",
  23. ".mp4": "video/mp4",
  24. ".wav": "audio/wav",
  25. ".jpg": "image/jpeg",
  26. ".jpeg": "image/jpeg",
  27. ".png": "image/png",
  28. }
  29. def main():
  30. ap = argparse.ArgumentParser()
  31. ap.add_argument("--workdir", required=True)
  32. ap.add_argument("--date", default="", help="覆盖日期(默认今天, YYYYMMDD)")
  33. args = ap.parse_args()
  34. wd = args.workdir
  35. report = os.path.join(wd, "report.html")
  36. if not os.path.exists(report):
  37. raise SystemExit("[publish] 缺少 report.html, 先跑 build_report.py")
  38. meta = (json.load(open(os.path.join(wd, "meta.json"), encoding="utf-8"))
  39. if os.path.exists(os.path.join(wd, "meta.json")) else {})
  40. platform = meta.get("platform") or "douyin"
  41. userid = get_userid()
  42. day = args.date or datetime.date.today().strftime("%Y%m%d")
  43. base = f"user/{userid}/report/lapian/{platform}/{day}"
  44. print(f"[publish] 目标前缀: {base}/ (identity userid={userid})")
  45. uploads = [] # (local, key)
  46. uploads.append((report, "report.html"))
  47. for name in ("video.mp4", "audio.wav", "transcript.json"):
  48. p = os.path.join(wd, name)
  49. if os.path.exists(p):
  50. uploads.append((p, name))
  51. fdir = os.path.join(wd, "frames")
  52. if os.path.isdir(fdir):
  53. for f in sorted(os.listdir(fdir)):
  54. if f.endswith(".jpg"):
  55. uploads.append((os.path.join(fdir, f), f"frames/{f}"))
  56. for name in ("analysis.json", "comments.json", "meta.json"):
  57. p = os.path.join(wd, name)
  58. if os.path.exists(p):
  59. uploads.append((p, f"assets/{name}"))
  60. urls = []
  61. for local, rel in uploads:
  62. key = f"{base}/{rel}"
  63. ct = MANIFEST_CT.get(os.path.splitext(rel)[1].lower())
  64. url = s3_put_file(local, key, ct)
  65. urls.append((rel, url))
  66. print(f"[publish] ✓ {rel} ({os.path.getsize(local)/1024:.0f} KB)")
  67. # 逐个 HTTP 校验(外部状态回读, 不只信 PUT 返回)
  68. fails = []
  69. for rel, url in urls:
  70. try:
  71. req = urllib.request.Request(url, method="HEAD")
  72. with urllib.request.urlopen(req, timeout=30) as r:
  73. if r.status != 200:
  74. fails.append((rel, r.status))
  75. except Exception as ex:
  76. fails.append((rel, str(ex)[:60]))
  77. if fails:
  78. print("[publish] WARN 以下产物校验失败:", fails)
  79. else:
  80. print(f"[publish] 全部 {len(urls)} 个产物公开可访问 ✓")
  81. report_url = public_url(f"{base}/report.html")
  82. print("\n=== 公开访问 ===")
  83. print(report_url)
  84. json.dump({"report_url": report_url, "prefix": base,
  85. "files": [{"name": rel, "url": u} for rel, u in urls]},
  86. open(os.path.join(wd, "publish.json"), "w", encoding="utf-8"),
  87. ensure_ascii=False, indent=2)
  88. if __name__ == "__main__":
  89. main()