结构调整
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user