结构调整

This commit is contained in:
2026-07-14 16:36:24 +08:00
parent 585e9d8e15
commit 57f354cf9f
254 changed files with 84114 additions and 44 deletions
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""
统计 Jellyfin 中每个演员出演的作品数量,找出超过指定阈值的演员。
结果保存为 JSON 和 Markdown 文档。
"""
import json
import os
import sys
import time
import urllib.request
# ====== 配置 ======
JELLYFIN_BASE = "http://192.168.3.5:8096"
AUTH_HEADER = 'MediaBrowser Client="OpenClaw", Device="PC", DeviceId="openclaw", Version="1.0", Token="8cbcfe4d7d294c8aa00412ccc853459f"'
USER_ID = "0f2540701e884852a2d2a1b7e63299e8"
MIN_MOVIES = 5
OUTPUT_DIR = "/mnt/f/docs"
# 需要检查的媒体类型
MEDIA_TYPES = "Movie,Series,Episode,Video"
def api_get(path, params=None):
"""通用 GET 请求"""
url = JELLYFIN_BASE + path
if params:
parts = []
for k, v in params.items():
if isinstance(v, bool):
v = "true" if v else "false"
elif isinstance(v, int):
v = str(v)
parts.append(f"{k}={urllib.request.quote(str(v))}")
url += "?" + "&".join(parts)
req = urllib.request.Request(url)
req.add_header("X-Emby-Authorization", AUTH_HEADER)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())
except urllib.error.HTTPError as e:
body = e.read().decode(errors="replace") if e.fp else ""
print(f" HTTP {e.code}: {e.reason} | {body[:200]}", file=sys.stderr)
return None
except Exception as e:
print(f" Error: {e}", file=sys.stderr)
return None
def main():
print("=== 获取所有演员 ===", file=sys.stderr)
# Persons API 无 limit 时返回全部
data = api_get("/Persons")
if not data:
print("ERROR: 无法获取演员列表", file=sys.stderr)
sys.exit(1)
persons = data.get("Items", [])
total_count = data.get("TotalRecordCount", len(persons))
print(f"总计 {total_count} 个演员,获取到 {len(persons)}", file=sys.stderr)
results = []
total = len(persons)
for i, p in enumerate(persons, 1):
name = p["Name"]
pid = p["Id"]
has_image = bool(p.get("ImageTags", {}))
# 查询该演员出演的媒体数量
movie_data = api_get(f"/Users/{USER_ID}/Items", {
"personIds": pid,
"recursive": True,
"includeItemTypes": MEDIA_TYPES,
"limit": 0,
})
count = movie_data.get("TotalRecordCount", 0) if movie_data else 0
if count >= MIN_MOVIES:
results.append({
"name": name,
"id": pid,
"movie_count": count,
"has_image": has_image,
})
if i % 30 == 0 or i == total:
print(f" 进度: {i}/{total} | 已找到 {len(results)} 人超过 {MIN_MOVIES}", file=sys.stderr)
time.sleep(0.03)
# 排序:作品数降序 → 姓名升序
results.sort(key=lambda x: (-x["movie_count"], x["name"]))
# ====== JSON ======
json_path = os.path.join(OUTPUT_DIR, "jellyfin_actor_stats.json")
with open(json_path, "w", encoding="utf-8") as f:
json.dump({
"total_persons": total_count,
"fetched_persons": len(persons),
"threshold": MIN_MOVIES,
"actors_above_threshold": len(results),
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
"actors": results,
}, f, ensure_ascii=False, indent=2)
print(f"\n✅ JSON: {json_path}", file=sys.stderr)
# ====== Markdown ======
md_path = os.path.join(OUTPUT_DIR, "jellyfin_actor_stats.md")
with open(md_path, "w", encoding="utf-8") as f:
f.write("# Jellyfin 演员作品统计\n\n")
f.write(f"- **演员总数**{total_count}\n")
f.write(f"- **统计阈值**:作品数 **> {MIN_MOVIES}** 部\n")
f.write(f"- **满足条件的演员****{len(results)}** 人\n")
f.write(f"- **统计时间**{time.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write("---\n\n")
# 用序号代表排名
rank = 0
prev = None
same_rank_start = 0
for idx, a in enumerate(results):
if a["movie_count"] != prev:
rank = idx + 1
prev = a["movie_count"]
img = "" if a["has_image"] else ""
f.write(f"| {rank} | {a['name']} | {a['movie_count']} | {img} |\n")
f.write("\n\n---\n\n")
f.write("*由脚本 `jellyfin_actor_stats.py` 自动生成*\n")
print(f"✅ Markdown: {md_path}", file=sys.stderr)
print(f"\n=== 结果摘要 ===", file=sys.stderr)
print(f"演员总数: {total_count}", file=sys.stderr)
print(f"超过 {MIN_MOVIES} 部: {len(results)}", file=sys.stderr)
top_n = min(20, len(results))
print(f"\n--- Top {top_n} ---", file=sys.stderr)
for a in results[:top_n]:
img = "🖼️" if a["has_image"] else " "
print(f" #{rank:3d} {a['name']:30s} {a['movie_count']:4d}{img}", file=sys.stderr)
if __name__ == "__main__":
main()