feat 限速插件支持智能限速

This commit is contained in:
thsrite
2023-08-31 17:05:47 +08:00
parent 8ad75e93a9
commit 143ffd18b7

View File

@ -1,3 +1,4 @@
import ipaddress
from typing import List, Tuple, Dict, Any
from apscheduler.schedulers.background import BackgroundScheduler
@ -50,6 +51,12 @@ class SpeedLimiter(_PluginBase):
_play_down_speed: float = 0
_noplay_up_speed: float = 0
_noplay_down_speed: float = 0
_bandwidth: float = 0
_allocation_ratio: str = ""
_auto_limit: bool = False
_limit_enabled: bool = False
# 不限速地址
_unlimited_ips = {"ipv4": "0.0.0.0/0", "ipv6": "::/0"}
# 当前限速状态
_current_state = ""
@ -62,6 +69,19 @@ class SpeedLimiter(_PluginBase):
self._play_down_speed = float(config.get("play_down_speed")) if config.get("play_down_speed") else 0
self._noplay_up_speed = float(config.get("noplay_up_speed")) if config.get("noplay_up_speed") else 0
self._noplay_down_speed = float(config.get("noplay_down_speed")) if config.get("noplay_down_speed") else 0
try:
# 总带宽
self._bandwidth = int(float(config.get("bandwidth") or 0)) * 1000000
except Exception:
self._bandwidth = 0
# 自动限速开关
self._auto_limit = True if self._bandwidth else False
self._allocation_ratio = config.get("allocation_ratio") or ""
# 不限速地址
self._unlimited_ips["ipv4"] = config.get("ipv4") or "0.0.0.0/0"
self._unlimited_ips["ipv6"] = config.get("ipv6") or "::/0"
if "0.0.0.0/0" in self._unlimited_ips["ipv4"] and "::/0" in self._unlimited_ips["ipv6"]:
self._limit_enabled = False
self._downloader = config.get("downloader") or []
if self._downloader:
if 'qbittorrent' in self._downloader:
@ -73,7 +93,7 @@ class SpeedLimiter(_PluginBase):
self.stop_service()
# 启动限速任务
if self._enabled:
if self._enabled and self._limit_enabled:
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
self._scheduler.add_job(func=self.check_playing_sessions,
trigger='interval',
@ -235,7 +255,85 @@ class SpeedLimiter(_PluginBase):
]
}
]
}
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'bandwidth',
'label': '智能限速上行带宽',
'placeholder': '设置上行带宽后,媒体服务器有媒体播放时根据上行带宽和媒体播放占用带宽计算上传限速数值。'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'allocation_ratio',
'label': '智能限速分配比例',
'placeholder': '多个下载器设置分配比例如两个下载器设置1:2,留空均分'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'ipv4',
'label': '不限速地址范围ipv4',
'placeholder': '192.168.1.0/24'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'ipv6',
'label': '不限速地址范围ipv6',
'placeholder': 'FE80::/10'
}
}
]
}
]
},
]
}
], {
@ -246,6 +344,10 @@ class SpeedLimiter(_PluginBase):
"play_down_speed": 0,
"noplay_up_speed": 0,
"noplay_down_speed": 0,
"bandwidth": 0,
"allocation_ratio": "",
"ipv4": "",
"ipv6": "",
}
def get_page(self) -> List[dict]:
@ -273,7 +375,8 @@ class SpeedLimiter(_PluginBase):
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"):
if not self.__allow_access(self._unlimited_ips, session.get("RemoteEndPoint")) and \
session.get("NowPlayingItem") and not session.get("PlayState", {}).get("IsPaused"):
playing_sessions.append(session)
except Exception as e:
logger.error(f"获取Emby播放会话失败{str(e)}")
@ -289,7 +392,8 @@ class SpeedLimiter(_PluginBase):
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"):
if not self.__allow_access(self._unlimited_ips, session.get("RemoteEndPoint")) and \
session.get("NowPlayingItem") and not session.get("PlayState", {}).get("IsPaused"):
playing_sessions.append(session)
except Exception as e:
logger.error(f"获取Jellyfin播放会话失败{str(e)}")
@ -313,11 +417,16 @@ class SpeedLimiter(_PluginBase):
})
# 计算有效比特率
for session in playing_sessions:
if not IpUtils.is_private_ip(session.get("address")) \
if not self.__allow_access(self._unlimited_ips, session.get("address")) and \
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 self._auto_limit:
self.__calc_limit(total_bit_rate)
# 当前正在播放,开始限速
self.__set_limiter(limit_type="播放", upload_limit=self._play_up_speed,
download_limit=self._play_down_speed)
@ -326,6 +435,16 @@ class SpeedLimiter(_PluginBase):
self.__set_limiter(limit_type="未播放", upload_limit=self._noplay_up_speed,
download_limit=self._noplay_down_speed)
def __calc_limit(self, total_bit_rate):
"""
计算智能上传限速
"""
residual_bandwidth = (self._bandwidth - total_bit_rate)
if residual_bandwidth < 0:
self._play_up_speed = 10
else:
self._play_up_speed = residual_bandwidth / 8 / 1024
def __set_limiter(self, limit_type: str, upload_limit: float, download_limit: float):
"""
设置限速
@ -348,45 +467,94 @@ class SpeedLimiter(_PluginBase):
else:
text = f"{text}\n下载:未限速"
try:
if self._qb:
self._qb.set_speed_limit(download_limit=download_limit, upload_limit=upload_limit)
# 发送通知
if self._notify:
title = "【播放限速】"
if upload_limit or download_limit:
subtitle = f"Qbittorrent 开始{limit_type}限速"
self.post_message(
mtype=NotificationType.MediaServer,
title=title,
text=f"{subtitle}\n{text}"
)
cnt = 0
for download in self._downloader:
if self._auto_limit and limit_type == "播放":
# 开启了播放智能限速
allocation_count = sum(self._allocation_ratio) if self._allocation_ratio else len(self._downloader)
if not self._allocation_ratio:
upload_limit = int(upload_limit / allocation_count)
else:
self.post_message(
mtype=NotificationType.MediaServer,
title=title,
text=f"Qbittorrent 已取消限速"
)
if self._tr:
self._tr.set_speed_limit(download_limit=download_limit, upload_limit=upload_limit)
# 发送通知
if self._notify:
title = "【播放限速】"
if upload_limit or download_limit:
subtitle = f"Transmission 开始{limit_type}限速"
self.post_message(
mtype=NotificationType.MediaServer,
title=title,
text=f"{subtitle}\n{text}"
)
upload_limit = int(upload_limit * float(self._allocation_ratio[cnt]) / allocation_count)
cnt += 1
if str(download) == 'qbittorrent':
if self._qb:
self._qb.set_speed_limit(download_limit=download_limit, upload_limit=upload_limit)
# 发送通知
if self._notify:
title = "【播放限速】"
if upload_limit or download_limit:
subtitle = f"Qbittorrent 开始{limit_type}限速"
self.post_message(
mtype=NotificationType.MediaServer,
title=title,
text=f"{subtitle}\n{text}"
)
else:
self.post_message(
mtype=NotificationType.MediaServer,
title=title,
text=f"Qbittorrent 已取消限速"
)
else:
self.post_message(
mtype=NotificationType.MediaServer,
title=title,
text=f"Transmission 已取消限速"
)
if self._tr:
self._tr.set_speed_limit(download_limit=download_limit, upload_limit=upload_limit)
# 发送通知
if self._notify:
title = "【播放限速】"
if upload_limit or download_limit:
subtitle = f"Transmission 开始{limit_type}限速"
self.post_message(
mtype=NotificationType.MediaServer,
title=title,
text=f"{subtitle}\n{text}"
)
else:
self.post_message(
mtype=NotificationType.MediaServer,
title=title,
text=f"Transmission 已取消限速"
)
except Exception as e:
logger.error(f"设置限速失败:{str(e)}")
@staticmethod
def __allow_access(allow_ips, ip):
"""
判断IP是否合法
:param allow_ips: 充许的IP范围 {"ipv4":, "ipv6":}
:param ip: 需要检查的ip
"""
if not allow_ips:
return True
try:
ipaddr = ipaddress.ip_address(ip)
if ipaddr.version == 4:
if not allow_ips.get('ipv4'):
return True
allow_ipv4s = allow_ips.get('ipv4').split(",")
for allow_ipv4 in allow_ipv4s:
if ipaddr in ipaddress.ip_network(allow_ipv4):
return True
elif ipaddr.ipv4_mapped:
if not allow_ips.get('ipv4'):
return True
allow_ipv4s = allow_ips.get('ipv4').split(",")
for allow_ipv4 in allow_ipv4s:
if ipaddr.ipv4_mapped in ipaddress.ip_network(allow_ipv4):
return True
else:
if not allow_ips.get('ipv6'):
return True
allow_ipv6s = allow_ips.get('ipv6').split(",")
for allow_ipv6 in allow_ipv6s:
if ipaddr in ipaddress.ip_network(allow_ipv6):
return True
except Exception:
return False
return False
def stop_service(self):
"""
退出插件