sync from WSL host: latest openclaw config update

This commit is contained in:
2026-07-14 16:58:43 +08:00
parent 4ca178cb25
commit 8865fb5aa7
339 changed files with 80661 additions and 21261 deletions
@@ -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()