Merge remote-tracking branch 'origin/wlj0909' into wlj0909

This commit is contained in:
mayun110 2023-09-27 18:05:46 +08:00
commit a89bd8b816
11 changed files with 347 additions and 316 deletions

View File

@ -85,15 +85,18 @@ def delete_transfer_history(history_in: schemas.TransferHistory,
@router.post("/transfer", summary="历史记录重新转移", response_model=schemas.Response) @router.post("/transfer", summary="历史记录重新转移", response_model=schemas.Response)
def redo_transfer_history(history_in: schemas.TransferHistory, def redo_transfer_history(history_in: schemas.TransferHistory,
mtype: str, mtype: str = None,
new_tmdbid: int, new_tmdbid: int = None,
db: Session = Depends(get_db), db: Session = Depends(get_db),
_: schemas.TokenPayload = Depends(verify_token)) -> Any: _: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
历史记录重新转移 历史记录重新转移不输入 mtype new_tmdbid 自动使用文件名重新识别
""" """
state, errmsg = TransferChain(db).re_transfer(logid=history_in.id, if mtype and new_tmdbid:
mtype=MediaType(mtype), tmdbid=new_tmdbid) state, errmsg = TransferChain(db).re_transfer(logid=history_in.id,
mtype=MediaType(mtype), tmdbid=new_tmdbid)
else:
state, errmsg = TransferChain(db).re_transfer(logid=history_in.id)
if state: if state:
return schemas.Response(success=True) return schemas.Response(success=True)
else: else:

View File

@ -4,7 +4,7 @@ from typing import Optional, List, Tuple
from app.chain import ChainBase from app.chain import ChainBase
from app.core.context import Context, MediaInfo from app.core.context import Context, MediaInfo
from app.core.meta import MetaBase from app.core.meta import MetaBase
from app.core.metainfo import MetaInfo from app.core.metainfo import MetaInfo, MetaInfoPath
from app.log import logger from app.log import logger
from app.utils.string import StringUtils from app.utils.string import StringUtils
@ -38,12 +38,8 @@ class MediaChain(ChainBase):
""" """
logger.info(f'开始识别媒体信息,文件:{path} ...') logger.info(f'开始识别媒体信息,文件:{path} ...')
file_path = Path(path) file_path = Path(path)
# 上级目录元数据 # 元数据
dir_meta = MetaInfo(title=file_path.parent.name) file_meta = MetaInfoPath(file_path)
# 文件元数据,不包含后缀
file_meta = MetaInfo(title=file_path.stem)
# 合并元数据
file_meta.merge(dir_meta)
# 识别媒体信息 # 识别媒体信息
mediainfo = self.recognize_media(meta=file_meta) mediainfo = self.recognize_media(meta=file_meta)
if not mediainfo: if not mediainfo:

View File

@ -12,7 +12,7 @@ from app.chain.media import MediaChain
from app.core.config import settings from app.core.config import settings
from app.core.context import MediaInfo from app.core.context import MediaInfo
from app.core.meta import MetaBase from app.core.meta import MetaBase
from app.core.metainfo import MetaInfo from app.core.metainfo import MetaInfoPath
from app.db.downloadhistory_oper import DownloadHistoryOper from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.models.downloadhistory import DownloadHistory from app.db.models.downloadhistory import DownloadHistory
from app.db.models.transferhistory import TransferHistory from app.db.models.transferhistory import TransferHistory
@ -202,12 +202,8 @@ class TransferChain(ChainBase):
key=ProgressKey.FileTransfer) key=ProgressKey.FileTransfer)
if not meta: if not meta:
# 上级目录元数据 # 文件元数据
dir_meta = MetaInfo(title=file_path.parent.name) file_meta = MetaInfoPath(file_path)
# 文件元数据,不包含后缀
file_meta = MetaInfo(title=file_path.stem)
# 合并元数据
file_meta.merge(dir_meta)
else: else:
file_meta = meta file_meta = meta
@ -474,7 +470,8 @@ class TransferChain(ChainBase):
text=errmsg, userid=userid)) text=errmsg, userid=userid))
return return
def re_transfer(self, logid: int, mtype: MediaType, tmdbid: int) -> Tuple[bool, str]: def re_transfer(self, logid: int,
mtype: MediaType = None, tmdbid: int = None) -> Tuple[bool, str]:
""" """
根据历史记录重新识别转移只处理对应的src目录 根据历史记录重新识别转移只处理对应的src目录
:param logid: 历史记录ID :param logid: 历史记录ID
@ -492,11 +489,15 @@ class TransferChain(ChainBase):
return False, f"源目录不存在:{src_path}" return False, f"源目录不存在:{src_path}"
dest_path = Path(history.dest) if history.dest else None dest_path = Path(history.dest) if history.dest else None
# 查询媒体信息 # 查询媒体信息
mediainfo = self.recognize_media(mtype=mtype, tmdbid=tmdbid) if mtype and tmdbid:
mediainfo = self.recognize_media(mtype=mtype, tmdbid=tmdbid)
else:
meta = MetaInfoPath(src_path)
mediainfo = self.recognize_media(meta=meta)
if not mediainfo: if not mediainfo:
return False, f"未识别到媒体信息,类型:{mtype.value}tmdbid{tmdbid}" return False, f"未识别到媒体信息,类型:{mtype.value}tmdbid{tmdbid}"
# 重新执行转移 # 重新执行转移
logger.info(f"{mtype.value} {tmdbid} 识别为:{mediainfo.title_year}") logger.info(f"{src_path.name} 识别为:{mediainfo.title_year}")
# 更新媒体图片 # 更新媒体图片
self.obtain_images(mediainfo=mediainfo) self.obtain_images(mediainfo=mediainfo)

View File

@ -9,7 +9,7 @@ from app.core.meta.words import WordsMatcher
def MetaInfo(title: str, subtitle: str = None) -> MetaBase: def MetaInfo(title: str, subtitle: str = None) -> MetaBase:
""" """
媒体整理入口根据名称和副标题判断是哪种类型的识别返回对应对象 根据标题和副标题识别元数据
:param title: 标题种子名文件名 :param title: 标题种子名文件名
:param subtitle: 副标题描述 :param subtitle: 副标题描述
:return: MetaAnimeMetaVideo :return: MetaAnimeMetaVideo
@ -33,6 +33,20 @@ def MetaInfo(title: str, subtitle: str = None) -> MetaBase:
return meta return meta
def MetaInfoPath(path: Path) -> MetaBase:
"""
根据路径识别元数据
:param path: 路径
"""
# 上级目录元数据
dir_meta = MetaInfo(title=path.parent.name)
# 文件元数据,不包含后缀
file_meta = MetaInfo(title=path.stem)
# 合并元数据
file_meta.merge(dir_meta)
return file_meta
def is_anime(name: str) -> bool: def is_anime(name: str) -> bool:
""" """
判断是否为动漫 判断是否为动漫

View File

@ -536,16 +536,14 @@ class FileTransferModule(_ModuleBase):
@staticmethod @staticmethod
def get_library_path(path: Path): def get_library_path(path: Path):
""" """
根据目录查询其所在的媒体库目录 根据目录查询其所在的媒体库目录查询不到的返回输入目录
""" """
if not path: if not path:
return None return None
if not settings.LIBRARY_PATHS: if not settings.LIBRARY_PATHS:
return None return path
# 目的路径,多路径以,分隔 # 目的路径,多路径以,分隔
dest_paths = settings.LIBRARY_PATHS dest_paths = settings.LIBRARY_PATHS
if len(dest_paths) == 1:
return dest_paths[0]
for libpath in dest_paths: for libpath in dest_paths:
try: try:
if path.is_relative_to(libpath): if path.is_relative_to(libpath):
@ -553,7 +551,7 @@ class FileTransferModule(_ModuleBase):
except Exception as e: except Exception as e:
logger.debug(f"计算媒体库路径时出错:{e}") logger.debug(f"计算媒体库路径时出错:{e}")
continue continue
return None return path
@staticmethod @staticmethod
def get_target_path(in_path: Path = None) -> Optional[Path]: def get_target_path(in_path: Path = None) -> Optional[Path]:

View File

@ -131,130 +131,130 @@ class BestFilmVersion(_PluginBase):
拼装插件配置页面需要返回两块数据1页面配置2数据结构 拼装插件配置页面需要返回两块数据1页面配置2数据结构
""" """
return [ return [
{ {
'component': 'VForm', 'component': 'VForm',
'content': [ 'content': [
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 3 'md': 3
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'enabled', 'model': 'enabled',
'label': '启用插件', 'label': '启用插件',
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 3 'md': 3
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'notify', 'model': 'notify',
'label': '发送通知', 'label': '发送通知',
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 3 'md': 3
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'only_once', 'model': 'only_once',
'label': '立即运行一次', 'label': '立即运行一次',
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 3 'md': 3
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'webhook_enabled', 'model': 'webhook_enabled',
'label': 'Webhook', 'label': 'Webhook',
} }
} }
] ]
} }
] ]
}, },
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
}, },
'content': [ 'content': [
{ {
'component': 'VTextField', 'component': 'VTextField',
'props': { 'props': {
'model': 'cron', 'model': 'cron',
'label': '执行周期', 'label': '执行周期',
'placeholder': '5位cron表达式留空自动' 'placeholder': '5位cron表达式留空自动'
} }
} }
] ]
} }
] ]
}, },
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
}, },
'content': [ 'content': [
{ {
'component': 'VAlert', 'component': 'VAlert',
'props': { 'props': {
'text': '支持主动定时获取媒体库数据和Webhook实时触发两种方式两者只能选其一' 'text': '支持主动定时获取媒体库数据和Webhook实时触发两种方式两者只能选其一'
'Webhook需要在媒体服务器设置发送Webhook报文。' 'Webhook需要在媒体服务器设置发送Webhook报文。'
'Plex使用主动获取时建议执行周期设置大于1小时' 'Plex使用主动获取时建议执行周期设置大于1小时'
'收藏Api调用Plex官网接口有频率限制。' '收藏Api调用Plex官网接口有频率限制。'
} }
} }
] ]
} }
] ]
} }
] ]
} }
], { ], {
"enabled": False, "enabled": False,
"notify": False, "notify": False,
"cron": "*/30 * * * *", "cron": "*/30 * * * *",
"webhook_enabled": False, "webhook_enabled": False,
"only_once": False "only_once": False
} }
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
""" """
@ -386,81 +386,85 @@ class BestFilmVersion(_PluginBase):
# 读取历史记录 # 读取历史记录
history = self.get_data('history') or [] history = self.get_data('history') or []
all_item = [] # 媒体服务器类型,多个以,分隔
if not settings.MEDIASERVER:
return
media_servers = settings.MEDIASERVER.split(',')
# 读取收藏 # 读取收藏
if settings.MEDIASERVER == 'jellyfin': all_items = {}
self.jellyfin_get_items(all_item) for media_server in media_servers:
elif settings.MEDIASERVER == 'emby': if media_server == 'jellyfin':
self.emby_get_items(all_item) all_items['jellyfin'] = self.jellyfin_get_items()
else: elif media_server == 'emby':
resp = self.plex_get_watchlist() all_items['emby'] = self.emby_get_items()
if not resp: else:
return all_items['plex'] = self.plex_get_watchlist()
all_item.extend(resp)
def function(y, x): def function(y, x):
return y if (x['Name'] in [i['Name'] for i in y]) else (lambda z, u: (z.append(u), z))(y, x)[1] return y if (x['Name'] in [i['Name'] for i in y]) else (lambda z, u: (z.append(u), z))(y, x)[1]
# all_item 根据电影名去重 # 处理所有结果
result = reduce(function, all_item, []) for server, all_item in all_items.items():
# all_item 根据电影名去重
for data in result: result = reduce(function, all_item, [])
# 检查缓存 for data in result:
if data.get('Name') in caches: # 检查缓存
continue if data.get('Name') in caches:
# 获取详情
if settings.MEDIASERVER == 'jellyfin':
item_info_resp = Jellyfin().get_iteminfo(itemid=data.get('Id'))
elif settings.MEDIASERVER == 'emby':
item_info_resp = Emby().get_iteminfo(itemid=data.get('Id'))
else:
item_info_resp = self.plex_get_iteminfo(itemid=data.get('Id'))
logger.info(f'BestFilmVersion插件 item打印 {item_info_resp}')
if not item_info_resp:
continue
# 只接受Movie类型
if data.get('Type') != 'Movie':
continue
# 获取tmdb_id
media_info_ids = item_info_resp.get('ExternalUrls')
if not media_info_ids:
continue
for media_info_id in media_info_ids:
if 'TheMovieDb' != media_info_id.get('Name'):
continue continue
tmdb_find_id = str(media_info_id.get('Url')).split('/')
tmdb_find_id.reverse() # 获取详情
tmdb_id = tmdb_find_id[0] if server == 'jellyfin':
# 识别媒体信息 item_info_resp = Jellyfin().get_iteminfo(itemid=data.get('Id'))
mediainfo: MediaInfo = self.chain.recognize_media(tmdbid=tmdb_id, mtype=MediaType.MOVIE) elif server == 'emby':
if not mediainfo: item_info_resp = Emby().get_iteminfo(itemid=data.get('Id'))
logger.warn(f'未识别到媒体信息,标题:{data.get("Name")}tmdbID{tmdb_id}') else:
item_info_resp = self.plex_get_iteminfo(itemid=data.get('Id'))
logger.info(f'BestFilmVersion插件 item打印 {item_info_resp}')
if not item_info_resp:
continue continue
# 添加订阅
self.subscribechain.add(mtype=MediaType.MOVIE, # 只接受Movie类型
title=mediainfo.title, if data.get('Type') != 'Movie':
year=mediainfo.year, continue
tmdbid=mediainfo.tmdb_id,
best_version=True, # 获取tmdb_id
username="收藏洗版", media_info_ids = item_info_resp.get('ExternalUrls')
exist_ok=True) if not media_info_ids:
# 加入缓存 continue
caches.append(data.get('Name')) for media_info_id in media_info_ids:
# 存储历史记录 if 'TheMovieDb' != media_info_id.get('Name'):
if mediainfo.tmdb_id not in [h.get("tmdbid") for h in history]: continue
history.append({ tmdb_find_id = str(media_info_id.get('Url')).split('/')
"title": mediainfo.title, tmdb_find_id.reverse()
"type": mediainfo.type.value, tmdb_id = tmdb_find_id[0]
"year": mediainfo.year, # 识别媒体信息
"poster": mediainfo.get_poster_image(), mediainfo: MediaInfo = self.chain.recognize_media(tmdbid=tmdb_id, mtype=MediaType.MOVIE)
"overview": mediainfo.overview, if not mediainfo:
"tmdbid": mediainfo.tmdb_id, logger.warn(f'未识别到媒体信息,标题:{data.get("Name")}tmdbID{tmdb_id}')
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") continue
}) # 添加订阅
self.subscribechain.add(mtype=MediaType.MOVIE,
title=mediainfo.title,
year=mediainfo.year,
tmdbid=mediainfo.tmdb_id,
best_version=True,
username="收藏洗版",
exist_ok=True)
# 加入缓存
caches.append(data.get('Name'))
# 存储历史记录
if mediainfo.tmdb_id not in [h.get("tmdbid") for h in history]:
history.append({
"title": mediainfo.title,
"type": mediainfo.type.value,
"year": mediainfo.year,
"poster": mediainfo.get_poster_image(),
"overview": mediainfo.overview,
"tmdbid": mediainfo.tmdb_id,
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
# 保存历史记录 # 保存历史记录
self.save_data('history', history) self.save_data('history', history)
# 保存缓存 # 保存缓存
@ -468,13 +472,14 @@ class BestFilmVersion(_PluginBase):
finally: finally:
lock.release() lock.release()
def jellyfin_get_items(self, all_item): def jellyfin_get_items(self) -> List[dict]:
# 获取所有user # 获取所有user
users_url = "{HOST}Users?&apikey={APIKEY}" users_url = "{HOST}Users?&apikey={APIKEY}"
users = self.get_users(Jellyfin().get_data(users_url)) users = self.get_users(Jellyfin().get_data(users_url))
if not users: if not users:
logger.info(f"bestfilmversion/users_url: {users_url}") logger.info(f"bestfilmversion/users_url: {users_url}")
return return []
all_items = []
for user in users: for user in users:
# 根据加入日期 降序排序 # 根据加入日期 降序排序
url = "{HOST}Users/" + user + "/Items?SortBy=DateCreated%2CSortName" \ url = "{HOST}Users/" + user + "/Items?SortBy=DateCreated%2CSortName" \
@ -490,14 +495,16 @@ class BestFilmVersion(_PluginBase):
resp = self.get_items(Jellyfin().get_data(url)) resp = self.get_items(Jellyfin().get_data(url))
if not resp: if not resp:
continue continue
all_item.extend(resp) all_items.extend(resp)
return all_items
def emby_get_items(self, all_item): def emby_get_items(self) -> List[dict]:
# 获取所有user # 获取所有user
get_users_url = "{HOST}Users?&api_key={APIKEY}" get_users_url = "{HOST}Users?&api_key={APIKEY}"
users = self.get_users(Emby().get_data(get_users_url)) users = self.get_users(Emby().get_data(get_users_url))
if not users: if not users:
return return []
all_items = []
for user in users: for user in users:
# 根据加入日期 降序排序 # 根据加入日期 降序排序
url = "{HOST}emby/Users/" + user + "/Items?SortBy=DateCreated%2CSortName" \ url = "{HOST}emby/Users/" + user + "/Items?SortBy=DateCreated%2CSortName" \
@ -512,7 +519,8 @@ class BestFilmVersion(_PluginBase):
resp = self.get_items(Emby().get_data(url)) resp = self.get_items(Emby().get_data(url))
if not resp: if not resp:
continue continue
all_item.extend(resp) all_items.extend(resp)
return all_items
@staticmethod @staticmethod
def get_items(resp: Response): def get_items(resp: Response):
@ -538,7 +546,7 @@ class BestFilmVersion(_PluginBase):
return [] return []
@staticmethod @staticmethod
def plex_get_watchlist(): def plex_get_watchlist() -> List[dict]:
# 根据加入日期 降序排序 # 根据加入日期 降序排序
url = f"https://metadata.provider.plex.tv/library/sections/watchlist/all?type=1&sort=addedAt%3Adesc" \ url = f"https://metadata.provider.plex.tv/library/sections/watchlist/all?type=1&sort=addedAt%3Adesc" \
f"&X-Plex-Container-Start=0&X-Plex-Container-Size=50" \ f"&X-Plex-Container-Start=0&X-Plex-Container-Size=50" \

View File

@ -43,7 +43,7 @@ class BrushFlow(_PluginBase):
# 加载顺序 # 加载顺序
plugin_order = 21 plugin_order = 21
# 可使用的用户级别 # 可使用的用户级别
auth_level = 3 auth_level = 2
# 私有属性 # 私有属性
siteshelper = None siteshelper = None

View File

@ -15,7 +15,7 @@ from watchdog.observers.polling import PollingObserver
from app.chain.transfer import TransferChain from app.chain.transfer import TransferChain
from app.core.config import settings from app.core.config import settings
from app.core.context import MediaInfo from app.core.context import MediaInfo
from app.core.metainfo import MetaInfo from app.core.metainfo import MetaInfoPath
from app.db.downloadhistory_oper import DownloadHistoryOper from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.transferhistory_oper import TransferHistoryOper from app.db.transferhistory_oper import TransferHistoryOper
from app.log import logger from app.log import logger
@ -237,13 +237,8 @@ class DirMonitor(_PluginBase):
logger.info(f"{event_path} 已整理过") logger.info(f"{event_path} 已整理过")
return return
# 上级目录元数据 # 元数据
meta = MetaInfo(title=file_path.parent.name) file_meta = MetaInfoPath(file_path)
# 文件元数据,不包含后缀
file_meta = MetaInfo(title=file_path.stem)
# 合并元数据
file_meta.merge(meta)
if not file_meta.name: if not file_meta.name:
logger.error(f"{file_path.name} 无法识别有效信息") logger.error(f"{file_path.name} 无法识别有效信息")
return return
@ -343,7 +338,7 @@ class DirMonitor(_PluginBase):
} }
""" """
# 发送消息汇总 # 发送消息汇总
media_list = self._medias.get(mediainfo.title_year + " " + meta.season) or {} media_list = self._medias.get(mediainfo.title_year + " " + file_meta.season) or {}
if media_list: if media_list:
media_files = media_list.get("files") or [] media_files = media_list.get("files") or []
if media_files: if media_files:
@ -384,7 +379,7 @@ class DirMonitor(_PluginBase):
], ],
"time": datetime.now() "time": datetime.now()
} }
self._medias[mediainfo.title_year + " " + meta.season] = media_list self._medias[mediainfo.title_year + " " + file_meta.season] = media_list
# 汇总刷新媒体库 # 汇总刷新媒体库
if settings.REFRESH_MEDIASERVER: if settings.REFRESH_MEDIASERVER:
@ -598,7 +593,7 @@ class DirMonitor(_PluginBase):
'rows': 5, 'rows': 5,
'placeholder': '每一行一个目录,支持两种配置方式:\n' 'placeholder': '每一行一个目录,支持两种配置方式:\n'
'监控目录\n' '监控目录\n'
'监控目录:转移目的目录' '监控目录:转移目的目录(需同时在媒体库目录中配置该目的目录)'
} }
} }
] ]

View File

@ -718,19 +718,21 @@ class MediaSyncDel(_PluginBase):
""" """
# 读取历史记录 # 读取历史记录
history = self.get_data('history') or [] history = self.get_data('history') or []
# 媒体服务器类型
media_server = settings.MEDIASERVER
last_time = self.get_data("last_time") last_time = self.get_data("last_time")
del_medias = [] del_medias = []
if media_server == 'emby':
del_medias = self.parse_emby_log(last_time) # 媒体服务器类型,多个以,分隔
elif media_server == 'jellyfin': if not settings.MEDIASERVER:
del_medias = self.parse_jellyfin_log(last_time)
elif media_server == 'plex':
# TODO plex解析日志
return return
media_servers = settings.MEDIASERVER.split(',')
for media_server in media_servers:
if media_server == 'emby':
del_medias.extend(self.parse_emby_log(last_time))
elif media_server == 'jellyfin':
del_medias.extend(self.parse_jellyfin_log(last_time))
elif media_server == 'plex':
# TODO plex解析日志
return
if not del_medias: if not del_medias:
logger.error("未解析到已删除媒体信息") logger.error("未解析到已删除媒体信息")

View File

@ -383,78 +383,86 @@ class SpeedLimiter(_PluginBase):
return return
# 当前播放的总比特率 # 当前播放的总比特率
total_bit_rate = 0 total_bit_rate = 0
# 查询播放中会话 # 媒体服务器类型,多个以,分隔
playing_sessions = [] if not settings.MEDIASERVER:
if settings.MEDIASERVER == "emby": return
req_url = "{HOST}emby/Sessions?api_key={APIKEY}" media_servers = settings.MEDIASERVER.split(',')
try: # 查询所有媒体服务器状态
res = Emby().get_data(req_url) for media_server in media_servers:
if res and res.status_code == 200: # 查询播放中会话
sessions = res.json() playing_sessions = []
for session in sessions: if media_server == "emby":
if session.get("NowPlayingItem") and not session.get("PlayState", {}).get("IsPaused"): req_url = "{HOST}emby/Sessions?api_key={APIKEY}"
playing_sessions.append(session) try:
except Exception as e: res = Emby().get_data(req_url)
logger.error(f"获取Emby播放会话失败{str(e)}") if res and res.status_code == 200:
# 计算有效比特率 sessions = res.json()
for session in playing_sessions: for session in sessions:
# 设置了不限速范围则判断session ip是否在不限速范围内 if session.get("NowPlayingItem") and not session.get("PlayState", {}).get("IsPaused"):
if self._unlimited_ips["ipv4"] or self._unlimited_ips["ipv6"]: playing_sessions.append(session)
if not self.__allow_access(self._unlimited_ips, session.get("RemoteEndPoint")) \ except Exception as e:
and session.get("NowPlayingItem", {}).get("MediaType") == "Video": logger.error(f"获取Emby播放会话失败{str(e)}")
total_bit_rate += int(session.get("NowPlayingItem", {}).get("Bitrate") or 0) continue
# 未设置不限速范围则默认不限速内网ip
elif not IpUtils.is_private_ip(session.get("RemoteEndPoint")) \
and session.get("NowPlayingItem", {}).get("MediaType") == "Video":
total_bit_rate += int(session.get("NowPlayingItem", {}).get("Bitrate") or 0)
elif settings.MEDIASERVER == "jellyfin":
req_url = "{HOST}Sessions?api_key={APIKEY}"
try:
res = Jellyfin().get_data(req_url)
if res and res.status_code == 200:
sessions = res.json()
for session in sessions:
if session.get("NowPlayingItem") and not session.get("PlayState", {}).get("IsPaused"):
playing_sessions.append(session)
except Exception as e:
logger.error(f"获取Jellyfin播放会话失败{str(e)}")
# 计算有效比特率
for session in playing_sessions:
# 设置了不限速范围则判断session ip是否在不限速范围内
if self._unlimited_ips["ipv4"] or self._unlimited_ips["ipv6"]:
if not self.__allow_access(self._unlimited_ips, session.get("RemoteEndPoint")) \
and session.get("NowPlayingItem", {}).get("MediaType") == "Video":
media_streams = session.get("NowPlayingItem", {}).get("MediaStreams") or []
for media_stream in media_streams:
total_bit_rate += int(media_stream.get("BitRate") or 0)
# 未设置不限速范围则默认不限速内网ip
elif not IpUtils.is_private_ip(session.get("RemoteEndPoint")) \
and session.get("NowPlayingItem", {}).get("MediaType") == "Video":
media_streams = session.get("NowPlayingItem", {}).get("MediaStreams") or []
for media_stream in media_streams:
total_bit_rate += int(media_stream.get("BitRate") or 0)
elif settings.MEDIASERVER == "plex":
_plex = Plex().get_plex()
if _plex:
sessions = _plex.sessions()
for session in sessions:
bitrate = sum([m.bitrate or 0 for m in session.media])
playing_sessions.append({
"type": session.TAG,
"bitrate": bitrate,
"address": session.player.address
})
# 计算有效比特率 # 计算有效比特率
for session in playing_sessions: for session in playing_sessions:
# 设置了不限速范围则判断session ip是否在不限速范围内 # 设置了不限速范围则判断session ip是否在不限速范围内
if self._unlimited_ips["ipv4"] or self._unlimited_ips["ipv6"]: if self._unlimited_ips["ipv4"] or self._unlimited_ips["ipv6"]:
if not self.__allow_access(self._unlimited_ips, session.get("address")) \ if not self.__allow_access(self._unlimited_ips, session.get("RemoteEndPoint")) \
and session.get("NowPlayingItem", {}).get("MediaType") == "Video":
total_bit_rate += int(session.get("NowPlayingItem", {}).get("Bitrate") or 0)
# 未设置不限速范围则默认不限速内网ip
elif not IpUtils.is_private_ip(session.get("RemoteEndPoint")) \
and session.get("NowPlayingItem", {}).get("MediaType") == "Video":
total_bit_rate += int(session.get("NowPlayingItem", {}).get("Bitrate") or 0)
elif media_server == "jellyfin":
req_url = "{HOST}Sessions?api_key={APIKEY}"
try:
res = Jellyfin().get_data(req_url)
if res and res.status_code == 200:
sessions = res.json()
for session in sessions:
if session.get("NowPlayingItem") and not session.get("PlayState", {}).get("IsPaused"):
playing_sessions.append(session)
except Exception as e:
logger.error(f"获取Jellyfin播放会话失败{str(e)}")
continue
# 计算有效比特率
for session in playing_sessions:
# 设置了不限速范围则判断session ip是否在不限速范围内
if self._unlimited_ips["ipv4"] or self._unlimited_ips["ipv6"]:
if not self.__allow_access(self._unlimited_ips, session.get("RemoteEndPoint")) \
and session.get("NowPlayingItem", {}).get("MediaType") == "Video":
media_streams = session.get("NowPlayingItem", {}).get("MediaStreams") or []
for media_stream in media_streams:
total_bit_rate += int(media_stream.get("BitRate") or 0)
# 未设置不限速范围则默认不限速内网ip
elif not IpUtils.is_private_ip(session.get("RemoteEndPoint")) \
and session.get("NowPlayingItem", {}).get("MediaType") == "Video":
media_streams = session.get("NowPlayingItem", {}).get("MediaStreams") or []
for media_stream in media_streams:
total_bit_rate += int(media_stream.get("BitRate") or 0)
elif media_server == "plex":
_plex = Plex().get_plex()
if _plex:
sessions = _plex.sessions()
for session in sessions:
bitrate = sum([m.bitrate or 0 for m in session.media])
playing_sessions.append({
"type": session.TAG,
"bitrate": bitrate,
"address": session.player.address
})
# 计算有效比特率
for session in playing_sessions:
# 设置了不限速范围则判断session ip是否在不限速范围内
if self._unlimited_ips["ipv4"] or self._unlimited_ips["ipv6"]:
if not self.__allow_access(self._unlimited_ips, session.get("address")) \
and session.get("type") == "Video":
total_bit_rate += int(session.get("bitrate") or 0)
# 未设置不限速范围则默认不限速内网ip
elif not IpUtils.is_private_ip(session.get("address")) \
and session.get("type") == "Video": and session.get("type") == "Video":
total_bit_rate += int(session.get("bitrate") or 0) total_bit_rate += int(session.get("bitrate") or 0)
# 未设置不限速范围则默认不限速内网ip
elif not IpUtils.is_private_ip(session.get("address")) \
and session.get("type") == "Video":
total_bit_rate += int(session.get("bitrate") or 0)
if total_bit_rate: if total_bit_rate:
# 开启智能限速计算上传限速 # 开启智能限速计算上传限速

View File

@ -61,7 +61,9 @@ class SystemUtils:
移动 移动
""" """
try: try:
# 当前目录改名
temp = src.replace(src.parent / dest.name) temp = src.replace(src.parent / dest.name)
# 移动到目标目录
shutil.move(temp, dest) shutil.move(temp, dest)
return 0, "" return 0, ""
except Exception as err: except Exception as err:
@ -74,7 +76,11 @@ class SystemUtils:
硬链接 硬链接
""" """
try: try:
dest.hardlink_to(src) # link到当前目录并改名
tmp_path = src.parent / dest.name
tmp_path.hardlink_to(src)
# 移动到目标目录
shutil.move(tmp_path, dest)
return 0, "" return 0, ""
except Exception as err: except Exception as err:
print(str(err)) print(str(err))