sync from WSL host: latest openclaw config update
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user