sync from WSL host: latest openclaw config update
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# Jellyfin API 配置
|
||||
|
||||
## 基本信息
|
||||
|
||||
- **服务器地址(WSL 内)**: http://127.0.0.1:8096
|
||||
- **服务器地址(Windows 宿主机)**: http://192.168.3.5:8096
|
||||
- **服务器地址(WSL 网关 IP)**: http://172.25.208.1:8096
|
||||
- **API Key**: `8cbcfe4d7d294c8aa00412ccc853459f`
|
||||
- **OpenAPI 规范文件**: `/mnt/f/docs/jellyfin-openapi-stable.json`
|
||||
- **用户 ID**: `0f2540701e884852a2d2a1b7e63299e8`
|
||||
|
||||
## 认证方式
|
||||
|
||||
```http
|
||||
X-Emby-Authorization: MediaBrowser Client="OpenClaw", Device="PC", DeviceId="openclaw", Version="1.0", Token="8cbcfe4d7d294c8aa00412ccc853459f"
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 搜索 API 参考: `jellyfin-search-api.md`
|
||||
- 重复电影检测: `jellyfin-dedup-howto.md`
|
||||
- 演员统计脚本: `../bt-scripts/jellyfin_actor_stats.py`
|
||||
@@ -0,0 +1,94 @@
|
||||
# Jellyfin 重复电影检测操作指南
|
||||
|
||||
> Jellyfin 服务器配置见 `jellyfin-config.md`
|
||||
|
||||
## 概述
|
||||
|
||||
通过 Jellyfin API 检测库中的重复电影,支持两种检测方式:
|
||||
- **相同名称+年份**:同一部电影被多次导入到不同文件夹
|
||||
- **相同文件大小+时长**:同一个文件被不同文件夹引用导致重复扫描入库(允许1%误差)
|
||||
|
||||
## 文件位置
|
||||
|
||||
```
|
||||
bt-docs/jellyfin-dedup.py # 检测脚本
|
||||
bt-docs/jellyfin-dedup-howto.md # 本文档
|
||||
bt-docs/jellyfin-config.md # Jellyfin 配置信息
|
||||
```
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. Jellyfin 服务已在 Windows 上运行(端口 8096)
|
||||
2. WSL 可通过网关 IP 访问 Windows 主机
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 运行检测
|
||||
|
||||
```bash
|
||||
python3 /mnt/f/docs/jellyfin-dedup.py
|
||||
```
|
||||
|
||||
> ⚠ 如果脚本在 WSL 中运行,注意 WSL 内访问 Windows 端口需要填写网关 IP。脚本内默认使用 `172.25.208.1:8096`,可直接修改脚本头部的 `SERVER_URL`。
|
||||
|
||||
### 输出说明
|
||||
|
||||
```
|
||||
============================================================
|
||||
Jellyfin 重复电影检测
|
||||
============================================================
|
||||
|
||||
📦 获取所有电影...
|
||||
已获取 100/753 部电影...
|
||||
共获取 753 部电影
|
||||
|
||||
🔍 开始检测重复...
|
||||
|
||||
⚠️ 发现 14 组重复
|
||||
============================================================
|
||||
|
||||
[1] 相同名称+年份
|
||||
原因: 电影名 'IPZZ-740 ...' (2025) 存在 2 个版本
|
||||
[1] 番号名称
|
||||
ID: e6955cab611255a7518af51492c15c15
|
||||
年份: 2025
|
||||
路径: E:\01_automated\日本电影\...
|
||||
大小: 5.12 GB
|
||||
简介: ...
|
||||
[2] 番号名称(破解版)
|
||||
ID: 224ead2dfa59084b0d9f9080b188f297
|
||||
年份: 2025
|
||||
路径: E:\01_automated\日本电影\...
|
||||
大小: 4.82 GB
|
||||
```
|
||||
|
||||
### 检测结果解读
|
||||
|
||||
| 检测类型 | 含义 | 处理建议 |
|
||||
|---------|------|---------|
|
||||
| **相同名称+年份** | 同一部电影在两个不同文件夹中 | 保留画质较好的版本,删除另一个 |
|
||||
| **相同文件大小+时长** | 同一个文件被多次扫描入库 | 删除 Jellyfin 中重复条目即可,无需删除文件 |
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 连接失败怎么办?
|
||||
A: 检查 Jellyfin 是否在运行,确认网关 IP 是否正确:
|
||||
```bash
|
||||
ip r | grep default # 查看默认网关
|
||||
```
|
||||
|
||||
### 为什么有些重复没检测到?
|
||||
A: 目前检测逻辑:
|
||||
- 相同番号不同文件名 → 能检测到
|
||||
- 同一文件软链接/硬链接 → 能检测到(文件大小相同时)
|
||||
- 不同画质版本(4K vs 1080p)→ 不会被标记(大小不同)
|
||||
|
||||
### 如何手动删除重复项?
|
||||
A: 在 Jellyfin Web UI 中找到重复电影 → 删除 → 在「文件和元数据」选项中勾选「删除文件」即可(注意别误删)。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- API 配置: `jellyfin-config.md`
|
||||
- 搜索 API: `jellyfin-search-api.md`
|
||||
- 检测脚本: `jellyfin-dedup.py`
|
||||
- 演员统计: `../bt-scripts/jellyfin_actor_stats.py`
|
||||
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Jellyfin 重复电影检测脚本
|
||||
通过文件名、ProviderIds、文件大小和时长判断重复
|
||||
"""
|
||||
|
||||
import requests
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
SERVER_URL = "http://172.25.208.1:8096"
|
||||
API_KEY = "8cbcfe4d7d294c8aa00412ccc853459f"
|
||||
|
||||
HEADERS = {
|
||||
"X-Emby-Authorization": f'MediaBrowser Client="OpenClaw", Device="PC", DeviceId="openclaw", Version="1.0", Token="{API_KEY}"',
|
||||
"Accept": "application/json"
|
||||
}
|
||||
|
||||
|
||||
def get_all_movies():
|
||||
"""获取所有电影"""
|
||||
movies = []
|
||||
start_index = 0
|
||||
limit = 100
|
||||
|
||||
while True:
|
||||
response = requests.get(
|
||||
f"{SERVER_URL}/Items",
|
||||
params={
|
||||
"includeItemTypes": "Movie",
|
||||
"recursive": "true",
|
||||
"fields": "Path,ProviderIds,MediaSources,RunTimeTicks,Overview,ProductionYear",
|
||||
"limit": limit,
|
||||
"startIndex": start_index,
|
||||
"enableTotalRecordCount": "true"
|
||||
},
|
||||
headers=HEADERS
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"请求失败: {response.status_code} {response.text[:200]}")
|
||||
break
|
||||
|
||||
data = response.json()
|
||||
items = data.get("Items", [])
|
||||
movies.extend(items)
|
||||
|
||||
total = data.get("TotalRecordCount", 0)
|
||||
start_index += limit
|
||||
|
||||
print(f" 已获取 {len(movies)}/{total} 部电影...", end="\r")
|
||||
if start_index >= total:
|
||||
break
|
||||
|
||||
print(f"\n 共获取 {len(movies)} 部电影")
|
||||
return movies
|
||||
|
||||
|
||||
def find_duplicates(movies):
|
||||
"""查找重复电影"""
|
||||
duplicates = []
|
||||
|
||||
# 1. 相同文件名(不同路径)
|
||||
name_map = defaultdict(list)
|
||||
for m in movies:
|
||||
name = (m.get("Name") or "").strip().lower()
|
||||
year = m.get("ProductionYear")
|
||||
key = (name, year)
|
||||
name_map[key].append(m)
|
||||
|
||||
for key, items in name_map.items():
|
||||
if len(items) > 1:
|
||||
duplicates.append({
|
||||
"type": "相同名称+年份",
|
||||
"reason": f"电影名 '{items[0].get('Name')}' ({key[1]}) 存在 {len(items)} 个版本",
|
||||
"items": items
|
||||
})
|
||||
|
||||
# 2. 相同 ProviderIds (IMDb/TMDb)
|
||||
provider_map = defaultdict(list)
|
||||
for m in movies:
|
||||
providers = m.get("ProviderIds") or {}
|
||||
for pid_type in ["Imdb", "Tmdb", "Tvdb"]:
|
||||
pid = providers.get(pid_type)
|
||||
if pid:
|
||||
provider_map[(pid_type, pid)].append(m)
|
||||
|
||||
for key, items in provider_map.items():
|
||||
if len(items) > 1:
|
||||
duplicates.append({
|
||||
"type": "相同 ProviderId",
|
||||
"reason": f"{key[0]} = {key[1]} 存在 {len(items)} 个版本",
|
||||
"items": items
|
||||
})
|
||||
|
||||
# 3. 相同文件大小和时长(不同路径 = 同文件被多次导入)
|
||||
size_map = defaultdict(list)
|
||||
for m in movies:
|
||||
sources = m.get("MediaSources") or []
|
||||
if sources:
|
||||
size = sources[0].get("Size")
|
||||
runtime = m.get("RunTimeTicks")
|
||||
if size and runtime:
|
||||
# 允许 1% 的误差
|
||||
size_key = (size // 1000000, runtime // 1000000)
|
||||
size_map[size_key].append((m, size, runtime))
|
||||
|
||||
for (size_gb, runtime_key), items in size_map.items():
|
||||
if len(items) > 1:
|
||||
# 检查是否来自不同路径
|
||||
paths = set(item[0].get("MediaSources", [{}])[0].get("Path", "") for item in items)
|
||||
if len(paths) == 1:
|
||||
continue # 同一文件,不重复标记
|
||||
duplicates.append({
|
||||
"type": "相同文件大小+时长",
|
||||
"reason": f"约 {size_gb/1000:.1f}GB / 约 {runtime_key//10000:.0f}分钟 存在 {len(items)} 个版本",
|
||||
"items": [item[0] for item in items]
|
||||
})
|
||||
|
||||
return duplicates
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Jellyfin 重复电影检测")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n📦 获取所有电影...")
|
||||
movies = get_all_movies()
|
||||
|
||||
if not movies:
|
||||
print("❌ 未获取到电影数据")
|
||||
return
|
||||
|
||||
print(f"\n🔍 开始检测重复...")
|
||||
all_dups = find_duplicates(movies)
|
||||
|
||||
if not all_dups:
|
||||
print("✅ 未发现重复电影")
|
||||
return
|
||||
|
||||
print(f"\n⚠️ 发现 {len(all_dups)} 组重复\n")
|
||||
print("=" * 60)
|
||||
|
||||
for i, dup in enumerate(all_dups, 1):
|
||||
print(f"\n[{i}] {dup['type']}")
|
||||
print(f" 原因: {dup['reason']}")
|
||||
print(f" {'─' * 50}")
|
||||
|
||||
for j, item in enumerate(dup['items'], 1):
|
||||
name = item.get("Name", "未知")
|
||||
year = item.get("ProductionYear", "未知")
|
||||
sources = item.get("MediaSources") or []
|
||||
path = sources[0].get("Path", "未知路径") if sources else "未知路径"
|
||||
size = sources[0].get("Size", 0) if sources else 0
|
||||
size_str = f"{size / 1024**3:.2f} GB" if size else "未知大小"
|
||||
movie_id = item.get("Id", "未知")
|
||||
overview = (item.get("Overview") or "")[:100]
|
||||
|
||||
print(f" [{j}] {name}")
|
||||
print(f" ID: {movie_id}")
|
||||
print(f" 年份: {year}")
|
||||
print(f" 路径: {path}")
|
||||
print(f" 大小: {size_str}")
|
||||
if overview:
|
||||
print(f" 简介: {overview}")
|
||||
|
||||
print()
|
||||
|
||||
print("=" * 60)
|
||||
print(f"总计 {len(all_dups)} 组重复,涉及 {
|
||||
len(set(item.get('Id') for dup in all_dups for item in dup['items']))
|
||||
} 部电影")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
# Jellyfin API 搜索电影
|
||||
|
||||
> 服务器地址和 API Key 见 `jellyfin-config.md`
|
||||
|
||||
## API 端点
|
||||
|
||||
```
|
||||
GET /Items
|
||||
```
|
||||
|
||||
## 搜索参数
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `searchTerm` | 搜索关键词 |
|
||||
| `userId` | 用户ID |
|
||||
| `limit` | 返回结果数量限制(默认 100) |
|
||||
| `startIndex` | 起始索引 |
|
||||
| `recursive` | 是否递归搜索(默认 true) |
|
||||
| `includeItemTypes` | 项目类型(如 "Movie", "Series", "Episode") |
|
||||
| `sortOrder` | 排序方式("Ascending", "Descending") |
|
||||
| `fields` | 返回额外字段 |
|
||||
| `minYear` / `maxYear` | 按年份筛选 |
|
||||
|
||||
## 请求示例
|
||||
|
||||
### 基础搜索
|
||||
```
|
||||
GET {ServerUrl}/Items?searchTerm=电影名&includeItemTypes=Movie
|
||||
X-Emby-Authorization: MediaBrowser Client="OpenClaw", Device="PC", DeviceId="openclaw", Version="1.0", Token="{ApiKey}"
|
||||
```
|
||||
|
||||
### 分页搜索
|
||||
```
|
||||
GET {ServerUrl}/Items?searchTerm=动作&includeItemTypes=Movie&limit=20&startIndex=0&sortOrder=Descending
|
||||
X-Emby-Authorization: MediaBrowser Client="OpenClaw", Device="PC", DeviceId="openclaw", Version="1.0", Token="{ApiKey}"
|
||||
```
|
||||
|
||||
### 按年份筛选
|
||||
```
|
||||
GET {ServerUrl}/Items?searchTerm=2024&includeItemTypes=Movie&minYear=2024
|
||||
X-Emby-Authorization: MediaBrowser Client="OpenClaw", Device="PC", DeviceId="openclaw", Version="1.0", Token="{ApiKey}"
|
||||
```
|
||||
|
||||
## Python 示例
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 从 jellyfin-config.md 读取配置
|
||||
SERVER_URL = "http://127.0.0.1:8096"
|
||||
API_KEY = "8cbcfe4d7d294c8aa00412ccc853459f"
|
||||
|
||||
headers = {
|
||||
"X-Emby-Authorization": f'MediaBrowser Client="OpenClaw", Device="PC", DeviceId="openclaw", Version="1.0", Token="{API_KEY}"'
|
||||
}
|
||||
|
||||
# 搜索电影
|
||||
response = requests.get(
|
||||
f"{SERVER_URL}/Items",
|
||||
params={
|
||||
"searchTerm": "电影名",
|
||||
"includeItemTypes": "Movie",
|
||||
"limit": 20,
|
||||
"startIndex": 0
|
||||
},
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
results = response.json()
|
||||
for item in results.get("Items", []):
|
||||
print(f"标题: {item.get('Name')}")
|
||||
print(f"年份: {item.get('ProductionYear')}")
|
||||
print(f"评分: {item.get('CommunityRating')}")
|
||||
print("-" * 50)
|
||||
else:
|
||||
print(f"错误: {response.status_code}")
|
||||
```
|
||||
|
||||
## 返回字段示例
|
||||
|
||||
```json
|
||||
{
|
||||
"Items": [
|
||||
{
|
||||
"Name": "电影标题",
|
||||
"Id": "项目ID",
|
||||
"Type": "Movie",
|
||||
"ProductionYear": 2024,
|
||||
"CommunityRating": 8.5,
|
||||
"Overview": "剧情简介",
|
||||
"RunTimeTicks": 1234567890,
|
||||
"Genres": ["动作", "剧情"],
|
||||
"MediaSources": [...]
|
||||
}
|
||||
],
|
||||
"TotalRecordCount": 100
|
||||
}
|
||||
```
|
||||
|
||||
## 其他端点
|
||||
|
||||
- `GET /Users/Me/Items` — 当前用户所有内容
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 配置: `jellyfin-config.md`
|
||||
- 重复电影检测: `jellyfin-dedup-howto.md`
|
||||
@@ -0,0 +1,579 @@
|
||||
{
|
||||
"total_persons": 610,
|
||||
"fetched_persons": 610,
|
||||
"threshold": 5,
|
||||
"actors_above_threshold": 95,
|
||||
"generated_at": "2026-05-09T22:14:35+0800",
|
||||
"actors": [
|
||||
{
|
||||
"name": "未知演员",
|
||||
"id": "e8d8f2d40d8b5f8730ab19820f772c2d",
|
||||
"movie_count": 58,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "河北彩花",
|
||||
"id": "b2b54f06f65bab8f67c4a677b4f88c27",
|
||||
"movie_count": 41,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "相泽南",
|
||||
"id": "74091e7a595c9b53eafe0f086b3ba2f3",
|
||||
"movie_count": 35,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "ドラゴン西川",
|
||||
"id": "7749f8835a045965b9bb2f1833881dc8",
|
||||
"movie_count": 29,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "肉尊",
|
||||
"id": "4865cf003c8bc3a6dd09c16ab47edb4b",
|
||||
"movie_count": 23,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "イナバール",
|
||||
"id": "58d800aeb115cdef4a290ed2322a2967",
|
||||
"movie_count": 19,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "前田文豪",
|
||||
"id": "8396709345f1f59a95d1dd14f0f8be18",
|
||||
"movie_count": 19,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "きとるね川口",
|
||||
"id": "26991347bee85f0b0f22cc4cfdaa64a2",
|
||||
"movie_count": 17,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "トレンディ山口",
|
||||
"id": "c99ed475e5b9ed571ef3804a996e8019",
|
||||
"movie_count": 16,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "キョウセイ",
|
||||
"id": "6d45c8b868301a05f3048675b5ad5d4d",
|
||||
"movie_count": 15,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "太宰珍歩",
|
||||
"id": "8a4e8f2c63856bb53d9a8c76bec7288e",
|
||||
"movie_count": 14,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "豆沢豆太郎",
|
||||
"id": "0d2fcf0f22a96d37012c8d652eec8d35",
|
||||
"movie_count": 14,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "Nagisa Futami",
|
||||
"id": "6b4ac3dda1122b1d0a4dc2435482afeb",
|
||||
"movie_count": 12,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "U吉",
|
||||
"id": "c2a59bf040a2900ff8c84ba5a3ec9d91",
|
||||
"movie_count": 12,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "冰青",
|
||||
"id": "1ad24f9af80d41529b93483790684af8",
|
||||
"movie_count": 11,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "嵐山みちる",
|
||||
"id": "8dca498d0cdff0b66101191a31ae69d1",
|
||||
"movie_count": 11,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "赤井彗星",
|
||||
"id": "eb4af3fd6d8c06ec4346b8bdc1203141",
|
||||
"movie_count": 11,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "TAKE-D",
|
||||
"id": "5fa992ab7834ba417465b8e09786336d",
|
||||
"movie_count": 10,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "大崎広浩治",
|
||||
"id": "1946a92abdcf62a7162e2065ab91ccfd",
|
||||
"movie_count": 10,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "明里䌷",
|
||||
"id": "4c5122465ed171c3075bbe284f49ea4a",
|
||||
"movie_count": 10,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "紋℃",
|
||||
"id": "ea5b1ca5d2ec8441db91323d0ba0cd00",
|
||||
"movie_count": 10,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "小凑よつ叶",
|
||||
"id": "9df7145dccdcb1d5e9d07964865c9197",
|
||||
"movie_count": 9,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "枫可怜",
|
||||
"id": "6e1cdf5553d0148055374feb9811efb9",
|
||||
"movie_count": 9,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "Hitoshi Kawamura",
|
||||
"id": "d717eea5994629ce181cf7c1a1ada4a9",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "Nana Kuwata",
|
||||
"id": "87276bbbda6596f72bd4bcab04c3cbee",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "Seiji Sayama",
|
||||
"id": "e6a769c33a3a723cf8486e7a8397fd31",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "Youichi Okazaki",
|
||||
"id": "15d0ba7fc4596211b064f75a2015ae98",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "ZAMPA",
|
||||
"id": "72ea1d908adafd30d65e629d2b051575",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "三島六三郎",
|
||||
"id": "1969bae065e6c481997573444c606515",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "宝瀬博教",
|
||||
"id": "6be0fe443f2497486331269042cbf082",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "宫西ひかる",
|
||||
"id": "86c08f5efca3b4bc0ec7cb5de9be2ebb",
|
||||
"movie_count": 8,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "朝霧浄",
|
||||
"id": "cd7dbed6999cfa5a8ee3687447dd7c3f",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "枫芙爱",
|
||||
"id": "bf31e038dec3ee22796aec84950af70f",
|
||||
"movie_count": 8,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "真咲南朋",
|
||||
"id": "c2f75da3ed433a459ebae6f4f222b46a",
|
||||
"movie_count": 8,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "神宫寺奈绪",
|
||||
"id": "1e24f1a6e8beca735230e50699480480",
|
||||
"movie_count": 8,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "苺原",
|
||||
"id": "aa66875a7316aa8e8cfcbad88e6ebece",
|
||||
"movie_count": 8,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "Tomoaki Ikeda",
|
||||
"id": "c40b40ba269bfa4bcfd0b97ee132eefc",
|
||||
"movie_count": 7,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "ひむろっく",
|
||||
"id": "b8606c4b32ddc0681faaa2876c844771",
|
||||
"movie_count": 7,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "五日市芽依",
|
||||
"id": "e3935226554960fa949725bc31dc26f4",
|
||||
"movie_count": 7,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "宫下玲奈",
|
||||
"id": "2857e08d5ad66d592e76c3add1f995fc",
|
||||
"movie_count": 7,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "小野六花",
|
||||
"id": "a948adec85d112ecb40466d369d49e8f",
|
||||
"movie_count": 7,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "平川大辅",
|
||||
"id": "7859ed30d7138150d42304bedc268bcc",
|
||||
"movie_count": 7,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "樱井浩美",
|
||||
"id": "d96b971ed9c4f0663a9c7879b1bd6ddb",
|
||||
"movie_count": 7,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "池田辉",
|
||||
"id": "7a323cc997f2e6f4e23fb83edb5a3035",
|
||||
"movie_count": 7,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "矢澤レシーブ",
|
||||
"id": "11878b202aeb1162704d60a0b80c1a46",
|
||||
"movie_count": 7,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "石川澪",
|
||||
"id": "4eaa79eb96bcbab0008c87aa4eae8e13",
|
||||
"movie_count": 7,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "花衣つばき",
|
||||
"id": "f6e1711d4067b6be0db9c20121a2427e",
|
||||
"movie_count": 7,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "金松季歩",
|
||||
"id": "f8a91138bf51fb08eded51ace5ea9a85",
|
||||
"movie_count": 7,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "鸣海惠梨香",
|
||||
"id": "04789e0674390fcb20a37be6bcc57948",
|
||||
"movie_count": 7,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "---",
|
||||
"id": "e1f2da1b63f837451696e9c7b8dff198",
|
||||
"movie_count": 6,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "Kiyomu Fukuda",
|
||||
"id": "dd02874bdb910c3f2f659cb23e0aad91",
|
||||
"movie_count": 6,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "らくだ",
|
||||
"id": "acf07aae4c35374e52a4a039c4fcb3f6",
|
||||
"movie_count": 6,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "キャプテン江原",
|
||||
"id": "25c0a67f956f7b781bd0f6152ed515b9",
|
||||
"movie_count": 6,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "七泽美亚",
|
||||
"id": "fe9371e7dcf00746dd33899c50908718",
|
||||
"movie_count": 6,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "七濑爱丽丝",
|
||||
"id": "508812a05991cffcdee3cb3646879e85",
|
||||
"movie_count": 6,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "松本一香",
|
||||
"id": "d51907b545527172aea47ae6e4578e4d",
|
||||
"movie_count": 6,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "永野善一",
|
||||
"id": "802834f8ecef6d4d43214cb86bea8799",
|
||||
"movie_count": 6,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "泉ももか",
|
||||
"id": "5dea4b38727f52d6ba34a6f28c8336e7",
|
||||
"movie_count": 6,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "翼舞",
|
||||
"id": "74da7a1427605ad5e346fc865224be9d",
|
||||
"movie_count": 6,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "香水纯",
|
||||
"id": "adedbb625fb1ca739ed7ddb999b09e27",
|
||||
"movie_count": 6,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "黑川纱里奈",
|
||||
"id": "7591cad07f4cdb1c17d57cd2ad9c8344",
|
||||
"movie_count": 6,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "Daifuku Suginami",
|
||||
"id": "255f6211d3ea974cf51cbcafc8ae7fa5",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "Haruna Kanbayashi",
|
||||
"id": "51b93dbbd1b0f09c66eb6e47e4fddc0e",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "うさぴょん。",
|
||||
"id": "7f2cfc65618362aac4cde35c3e88be39",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "ザック荒井",
|
||||
"id": "449c2953bf24b759fbb6e55645e7e18b",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "ペイペイ",
|
||||
"id": "689f41fa96301c796a3b0855be7f2145",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "七嶋舞",
|
||||
"id": "c0edc7148511a194d690112fc4b291de",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "七森莉莉",
|
||||
"id": "25dfc81b6ba21629859e3c0b7d21c7a9",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "三宫椿",
|
||||
"id": "8171c2b3b26dd7f6a38e90482fbed055",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "五右衛門",
|
||||
"id": "3295cd3dec0b0624eb5f21077b1ef2a1",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "伊藤舞雪",
|
||||
"id": "7c65633ac5495ed4e9121aebada51b67",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "儿玉七海",
|
||||
"id": "2010f14c740ccb1383f2cdba173f1a1f",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "八挂海",
|
||||
"id": "1851a02eee55a9bd797b6917d80dc8fc",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "加州夏",
|
||||
"id": "ff903a622f242dc3c26bacb362a5c16e",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "古川ほのか",
|
||||
"id": "bc6d4ad211f9432f7283b24d64ba5edd",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "坂道美琉",
|
||||
"id": "2cf74e40672241763504ff5a8d43aa0b",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "夢実かなえ",
|
||||
"id": "8a6a473e9703dd49f667be08fe5aba23",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "奥田咲",
|
||||
"id": "33c9d2419470b473d0d743970ad370cc",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "小春日和",
|
||||
"id": "996ad4d19a2bb6dcee4e7eb21c0a14ac",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "小松セブンティーン",
|
||||
"id": "4169d927a0d22f0f5cdfb781655d0a13",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "小野坂ゆいか",
|
||||
"id": "b8f734aadb27a50ce8c1c77cb1b49af5",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "彩月七绪",
|
||||
"id": "75e2d4712457e35cc9dc96fb4f946634",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "新井莉麻",
|
||||
"id": "c5c54c495fefaaebe2d5b360570e6e54",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "桃乃木香奈",
|
||||
"id": "1db1f00e6a326172af09881b6ee6b007",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "桥本有菜",
|
||||
"id": "8413ac2b973aeb70520e71247aa5b75c",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "横山宏美",
|
||||
"id": "cac408ba1de118700181f5981e59ea61",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "樱空桃",
|
||||
"id": "ebd7bec14516fce73f6cb7dd7e235de2",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "浅野こころ",
|
||||
"id": "eec3a2cbcf6d3b00c6b98c8bcaf7f656",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "海老咲あお",
|
||||
"id": "e9ec442fcd9551a5c8fe1c3ffb082a02",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "瑞泽溪",
|
||||
"id": "a60056fe619b9f9edadf2ca3ae8a0ee1",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "磯井啓",
|
||||
"id": "14d2ea40698c14da1b5a79e02632eb5d",
|
||||
"movie_count": 5,
|
||||
"has_image": false
|
||||
},
|
||||
{
|
||||
"name": "美乃雀",
|
||||
"id": "eb3c73722365fdda41eae78ad11b0f05",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "美谷朱音",
|
||||
"id": "964ca30628a2981860a3ed25969286aa",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "辻井穗乃果",
|
||||
"id": "20490dd2356ce2bd16e469cc3357cb21",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
},
|
||||
{
|
||||
"name": "鳳みゆ",
|
||||
"id": "fe7091114820db367fefb7521cdd45c1",
|
||||
"movie_count": 5,
|
||||
"has_image": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
# Jellyfin 演员作品统计
|
||||
|
||||
- **演员总数**:610
|
||||
- **统计阈值**:作品数 **> 5** 部
|
||||
- **满足条件的演员**:**95** 人
|
||||
- **统计时间**:2026-05-09 22:14:35
|
||||
|
||||
---
|
||||
|
||||
| 1 | 未知演员 | 58 | ❌ |
|
||||
| 2 | 河北彩花 | 41 | ✅ |
|
||||
| 3 | 相泽南 | 35 | ✅ |
|
||||
| 4 | ドラゴン西川 | 29 | ❌ |
|
||||
| 5 | 肉尊 | 23 | ❌ |
|
||||
| 6 | イナバール | 19 | ❌ |
|
||||
| 6 | 前田文豪 | 19 | ❌ |
|
||||
| 8 | きとるね川口 | 17 | ❌ |
|
||||
| 9 | トレンディ山口 | 16 | ❌ |
|
||||
| 10 | キョウセイ | 15 | ❌ |
|
||||
| 11 | 太宰珍歩 | 14 | ❌ |
|
||||
| 11 | 豆沢豆太郎 | 14 | ❌ |
|
||||
| 13 | Nagisa Futami | 12 | ❌ |
|
||||
| 13 | U吉 | 12 | ❌ |
|
||||
| 15 | 冰青 | 11 | ❌ |
|
||||
| 15 | 嵐山みちる | 11 | ❌ |
|
||||
| 15 | 赤井彗星 | 11 | ❌ |
|
||||
| 18 | TAKE-D | 10 | ❌ |
|
||||
| 18 | 大崎広浩治 | 10 | ❌ |
|
||||
| 18 | 明里䌷 | 10 | ✅ |
|
||||
| 18 | 紋℃ | 10 | ❌ |
|
||||
| 22 | 小凑よつ叶 | 9 | ✅ |
|
||||
| 22 | 枫可怜 | 9 | ✅ |
|
||||
| 24 | Hitoshi Kawamura | 8 | ❌ |
|
||||
| 24 | Nana Kuwata | 8 | ❌ |
|
||||
| 24 | Seiji Sayama | 8 | ❌ |
|
||||
| 24 | Youichi Okazaki | 8 | ❌ |
|
||||
| 24 | ZAMPA | 8 | ❌ |
|
||||
| 24 | 三島六三郎 | 8 | ❌ |
|
||||
| 24 | 宝瀬博教 | 8 | ❌ |
|
||||
| 24 | 宫西ひかる | 8 | ✅ |
|
||||
| 24 | 朝霧浄 | 8 | ❌ |
|
||||
| 24 | 枫芙爱 | 8 | ✅ |
|
||||
| 24 | 真咲南朋 | 8 | ✅ |
|
||||
| 24 | 神宫寺奈绪 | 8 | ✅ |
|
||||
| 24 | 苺原 | 8 | ❌ |
|
||||
| 37 | Tomoaki Ikeda | 7 | ❌ |
|
||||
| 37 | ひむろっく | 7 | ❌ |
|
||||
| 37 | 五日市芽依 | 7 | ✅ |
|
||||
| 37 | 宫下玲奈 | 7 | ✅ |
|
||||
| 37 | 小野六花 | 7 | ✅ |
|
||||
| 37 | 平川大辅 | 7 | ❌ |
|
||||
| 37 | 樱井浩美 | 7 | ❌ |
|
||||
| 37 | 池田辉 | 7 | ❌ |
|
||||
| 37 | 矢澤レシーブ | 7 | ❌ |
|
||||
| 37 | 石川澪 | 7 | ✅ |
|
||||
| 37 | 花衣つばき | 7 | ✅ |
|
||||
| 37 | 金松季歩 | 7 | ✅ |
|
||||
| 37 | 鸣海惠梨香 | 7 | ❌ |
|
||||
| 50 | --- | 6 | ❌ |
|
||||
| 50 | Kiyomu Fukuda | 6 | ❌ |
|
||||
| 50 | らくだ | 6 | ❌ |
|
||||
| 50 | キャプテン江原 | 6 | ❌ |
|
||||
| 50 | 七泽美亚 | 6 | ✅ |
|
||||
| 50 | 七濑爱丽丝 | 6 | ✅ |
|
||||
| 50 | 松本一香 | 6 | ✅ |
|
||||
| 50 | 永野善一 | 6 | ❌ |
|
||||
| 50 | 泉ももか | 6 | ✅ |
|
||||
| 50 | 翼舞 | 6 | ✅ |
|
||||
| 50 | 香水纯 | 6 | ✅ |
|
||||
| 50 | 黑川纱里奈 | 6 | ✅ |
|
||||
| 62 | Daifuku Suginami | 5 | ❌ |
|
||||
| 62 | Haruna Kanbayashi | 5 | ❌ |
|
||||
| 62 | うさぴょん。 | 5 | ❌ |
|
||||
| 62 | ザック荒井 | 5 | ❌ |
|
||||
| 62 | ペイペイ | 5 | ❌ |
|
||||
| 62 | 七嶋舞 | 5 | ✅ |
|
||||
| 62 | 七森莉莉 | 5 | ✅ |
|
||||
| 62 | 三宫椿 | 5 | ✅ |
|
||||
| 62 | 五右衛門 | 5 | ❌ |
|
||||
| 62 | 伊藤舞雪 | 5 | ✅ |
|
||||
| 62 | 儿玉七海 | 5 | ❌ |
|
||||
| 62 | 八挂海 | 5 | ✅ |
|
||||
| 62 | 加州夏 | 5 | ❌ |
|
||||
| 62 | 古川ほのか | 5 | ✅ |
|
||||
| 62 | 坂道美琉 | 5 | ✅ |
|
||||
| 62 | 夢実かなえ | 5 | ✅ |
|
||||
| 62 | 奥田咲 | 5 | ✅ |
|
||||
| 62 | 小春日和 | 5 | ❌ |
|
||||
| 62 | 小松セブンティーン | 5 | ❌ |
|
||||
| 62 | 小野坂ゆいか | 5 | ✅ |
|
||||
| 62 | 彩月七绪 | 5 | ❌ |
|
||||
| 62 | 新井莉麻 | 5 | ✅ |
|
||||
| 62 | 桃乃木香奈 | 5 | ✅ |
|
||||
| 62 | 桥本有菜 | 5 | ✅ |
|
||||
| 62 | 横山宏美 | 5 | ❌ |
|
||||
| 62 | 樱空桃 | 5 | ✅ |
|
||||
| 62 | 浅野こころ | 5 | ✅ |
|
||||
| 62 | 海老咲あお | 5 | ✅ |
|
||||
| 62 | 瑞泽溪 | 5 | ❌ |
|
||||
| 62 | 磯井啓 | 5 | ❌ |
|
||||
| 62 | 美乃雀 | 5 | ✅ |
|
||||
| 62 | 美谷朱音 | 5 | ✅ |
|
||||
| 62 | 辻井穗乃果 | 5 | ✅ |
|
||||
| 62 | 鳳みゆ | 5 | ✅ |
|
||||
|
||||
|
||||
---
|
||||
|
||||
*由脚本 `jellyfin_actor_stats.py` 自动生成*
|
||||
@@ -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