smoke_video_native.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. """一次性实验:抖音视频 → 直连 Gemini 原生视频 → 按时间戳分段提炼创作知识。
  2. 验证"整段视频交给 Gemini 能否详细提炼",对比抽帧。不接流水线,仅评估。
  3. 直连 Gemini File API(GEMINI_API_KEY),不走 OpenRouter(其对 Gemini 视频仅支持 YouTube)。
  4. 用法:python scripts/smoke_video_native.py [env_file] [content_id]
  5. """
  6. from __future__ import annotations
  7. import json
  8. import os
  9. import sys
  10. import time
  11. import httpx
  12. BASE = "https://generativelanguage.googleapis.com"
  13. DETAIL = "http://crawler.aiddit.com/crawler/dou_yin/detail"
  14. IOS_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15"
  15. PROMPT = (
  16. "你在从一条短视频里提炼「创作知识」——能指导别人怎么做内容的方法/原理/清单。\n"
  17. "视频是口播+画面,请**同时听口播、看画面与字幕**。按视频时间顺序分段,"
  18. "每段给时间戳和这段讲到的创作知识。原文(含口播)没有的不要编造。只输出 JSON:\n"
  19. '{"video_title": "", "overall": "全片在教什么,一两句话",\n'
  20. ' "segments": [{"start": "MM:SS", "end": "MM:SS", "title": "这段小标题",\n'
  21. ' "knowledge_types": ["what/why/how"], "what": "或null", "why": "或null", "how": "或null"}]}'
  22. )
  23. def load_env(path):
  24. env = {}
  25. for line in open(path):
  26. line = line.strip()
  27. if line and not line.startswith("#") and "=" in line:
  28. k, v = line.split("=", 1)
  29. env[k.strip()] = v.strip().strip('"').strip("'")
  30. return env
  31. def main():
  32. args = sys.argv[1:]
  33. env_file = next((a for a in args if a.endswith(".env")), ".env")
  34. video_file = next((a for a in args if a.endswith((".mp4", ".bin"))), None)
  35. cid = next((a for a in args if not a.endswith((".env", ".mp4", ".bin"))),
  36. "7612631899479648866")
  37. env = load_env(env_file)
  38. key = env.get("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY")
  39. if not key:
  40. raise SystemExit("缺 GEMINI_API_KEY(env_file 或环境变量)")
  41. model = (env.get("OPENROUTER_MODEL") or "gemini-3-flash-preview").split("/")[-1]
  42. if video_file: # 已有本地 mp4(如别处下载后传来),跳过抓取
  43. data = open(video_file, "rb").read()
  44. print(f"[1-2] 使用本地视频 {video_file}:{len(data)} bytes ({len(data)//1024//1024} MB)")
  45. else:
  46. print(f"[1] 取新鲜视频直链 content_id={cid}")
  47. inner = httpx.post(DETAIL, json={"content_id": cid}, timeout=40).json()["data"]["data"]
  48. vurl = inner["video_url_list"][0]["video_url"]
  49. print(" title:", inner.get("title"), "| dur:",
  50. inner["video_url_list"][0].get("video_duration"), "s")
  51. print("[2] 下载 mp4")
  52. with httpx.stream("GET", vurl, headers={"User-Agent": IOS_UA, "Referer": "https://www.douyin.com/"},
  53. timeout=180, follow_redirects=True) as r:
  54. r.raise_for_status()
  55. data = b"".join(r.iter_bytes())
  56. print(f" {len(data)} bytes ({len(data)//1024//1024} MB)")
  57. print("[3] File API 上传")
  58. start = httpx.post(
  59. f"{BASE}/upload/v1beta/files?key={key}",
  60. headers={"X-Goog-Upload-Protocol": "resumable", "X-Goog-Upload-Command": "start",
  61. "X-Goog-Upload-Header-Content-Length": str(len(data)),
  62. "X-Goog-Upload-Header-Content-Type": "video/mp4",
  63. "Content-Type": "application/json"},
  64. json={"file": {"display_name": "douyin_" + cid}}, timeout=60)
  65. start.raise_for_status()
  66. up_url = start.headers["X-Goog-Upload-URL"]
  67. up = httpx.post(up_url, headers={"X-Goog-Upload-Offset": "0",
  68. "X-Goog-Upload-Command": "upload, finalize"},
  69. content=data, timeout=600)
  70. up.raise_for_status()
  71. fobj = up.json()["file"]
  72. name, uri = fobj["name"], fobj["uri"]
  73. print(" file:", name)
  74. print("[4] 等待处理 ACTIVE")
  75. for _ in range(80):
  76. f = httpx.get(f"{BASE}/v1beta/{name}?key={key}", timeout=30).json()
  77. st = f.get("state")
  78. if st == "ACTIVE":
  79. break
  80. if st == "FAILED":
  81. raise SystemExit("文件处理失败: " + json.dumps(f, ensure_ascii=False))
  82. time.sleep(3)
  83. print(" state:", st)
  84. print(f"[5] generateContent(model={model},按时间戳提炼)")
  85. body = {"contents": [{"parts": [{"file_data": {"mime_type": "video/mp4", "file_uri": uri}},
  86. {"text": PROMPT}]}]}
  87. for m in [model, "gemini-2.5-flash", "gemini-flash-latest"]:
  88. g = httpx.post(f"{BASE}/v1beta/models/{m}:generateContent?key={key}", json=body, timeout=300)
  89. if g.status_code == 200:
  90. model = m
  91. break
  92. print(f" model {m} -> HTTP {g.status_code}: {g.text[:160]}")
  93. g.raise_for_status()
  94. text = g.json()["candidates"][0]["content"]["parts"][0]["text"]
  95. print(" 使用模型:", model)
  96. print("==== 提炼结果 ====")
  97. print(text)
  98. if __name__ == "__main__":
  99. raise SystemExit(main())