Files
openclaw-config/workspace-storage/jellyfin/jellyfin_actor_stats.py
T

149 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()