mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-16 13:55:28 +08:00
feat:支持订阅绑定类别和自定义识别词
This commit is contained in:
@@ -207,7 +207,8 @@ class DownloadChain(ChainBase):
|
|||||||
save_path: str = None,
|
save_path: str = None,
|
||||||
userid: Union[str, int] = None,
|
userid: Union[str, int] = None,
|
||||||
username: str = None,
|
username: str = None,
|
||||||
downloader: str = None) -> Optional[str]:
|
downloader: str = None,
|
||||||
|
media_category: str = None) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
下载及发送通知
|
下载及发送通知
|
||||||
:param context: 资源上下文
|
:param context: 资源上下文
|
||||||
@@ -219,6 +220,7 @@ class DownloadChain(ChainBase):
|
|||||||
:param userid: 用户ID
|
:param userid: 用户ID
|
||||||
:param username: 调用下载的用户名/插件名
|
:param username: 调用下载的用户名/插件名
|
||||||
:param downloader: 下载器
|
:param downloader: 下载器
|
||||||
|
:param media_category: 自定义媒体类别
|
||||||
"""
|
"""
|
||||||
_torrent = context.torrent_info
|
_torrent = context.torrent_info
|
||||||
_media = context.media_info
|
_media = context.media_info
|
||||||
@@ -318,7 +320,8 @@ class DownloadChain(ChainBase):
|
|||||||
userid=userid,
|
userid=userid,
|
||||||
username=username,
|
username=username,
|
||||||
channel=channel.value if channel else None,
|
channel=channel.value if channel else None,
|
||||||
date=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
date=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||||
|
media_category=media_category
|
||||||
)
|
)
|
||||||
|
|
||||||
# 登记下载文件
|
# 登记下载文件
|
||||||
@@ -382,7 +385,8 @@ class DownloadChain(ChainBase):
|
|||||||
channel: MessageChannel = None,
|
channel: MessageChannel = None,
|
||||||
source: str = None,
|
source: str = None,
|
||||||
userid: str = None,
|
userid: str = None,
|
||||||
username: str = None
|
username: str = None,
|
||||||
|
media_category: str = None
|
||||||
) -> Tuple[List[Context], Dict[Union[int, str], Dict[int, NotExistMediaInfo]]]:
|
) -> Tuple[List[Context], Dict[Union[int, str], Dict[int, NotExistMediaInfo]]]:
|
||||||
"""
|
"""
|
||||||
根据缺失数据,自动种子列表中组合择优下载
|
根据缺失数据,自动种子列表中组合择优下载
|
||||||
@@ -393,6 +397,7 @@ class DownloadChain(ChainBase):
|
|||||||
:param source: 通知来源
|
:param source: 通知来源
|
||||||
:param userid: 用户ID
|
:param userid: 用户ID
|
||||||
:param username: 调用下载的用户名/插件名
|
:param username: 调用下载的用户名/插件名
|
||||||
|
:param media_category: 自定义媒体类别
|
||||||
:return: 已经下载的资源列表、剩余未下载到的剧集 no_exists[tmdb_id/douban_id] = {season: NotExistMediaInfo}
|
:return: 已经下载的资源列表、剩余未下载到的剧集 no_exists[tmdb_id/douban_id] = {season: NotExistMediaInfo}
|
||||||
"""
|
"""
|
||||||
# 已下载的项目
|
# 已下载的项目
|
||||||
@@ -461,7 +466,8 @@ class DownloadChain(ChainBase):
|
|||||||
if context.media_info.type == MediaType.MOVIE:
|
if context.media_info.type == MediaType.MOVIE:
|
||||||
logger.info(f"开始下载电影 {context.torrent_info.title} ...")
|
logger.info(f"开始下载电影 {context.torrent_info.title} ...")
|
||||||
if self.download_single(context, save_path=save_path, channel=channel,
|
if self.download_single(context, save_path=save_path, channel=channel,
|
||||||
source=source, userid=userid, username=username):
|
source=source, userid=userid, username=username,
|
||||||
|
media_category=media_category):
|
||||||
# 下载成功
|
# 下载成功
|
||||||
logger.info(f"{context.torrent_info.title} 添加下载成功")
|
logger.info(f"{context.torrent_info.title} 添加下载成功")
|
||||||
downloaded_list.append(context)
|
downloaded_list.append(context)
|
||||||
@@ -543,14 +549,16 @@ class DownloadChain(ChainBase):
|
|||||||
channel=channel,
|
channel=channel,
|
||||||
source=source,
|
source=source,
|
||||||
userid=userid,
|
userid=userid,
|
||||||
username=username
|
username=username,
|
||||||
|
media_category=media_category
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 下载
|
# 下载
|
||||||
logger.info(f"开始下载 {torrent.title} ...")
|
logger.info(f"开始下载 {torrent.title} ...")
|
||||||
download_id = self.download_single(context, save_path=save_path,
|
download_id = self.download_single(context, save_path=save_path,
|
||||||
channel=channel, source=source,
|
channel=channel, source=source,
|
||||||
userid=userid, username=username)
|
userid=userid, username=username,
|
||||||
|
media_category=media_category)
|
||||||
|
|
||||||
if download_id:
|
if download_id:
|
||||||
# 下载成功
|
# 下载成功
|
||||||
@@ -618,7 +626,8 @@ class DownloadChain(ChainBase):
|
|||||||
logger.info(f"开始下载 {meta.title} ...")
|
logger.info(f"开始下载 {meta.title} ...")
|
||||||
download_id = self.download_single(context, save_path=save_path,
|
download_id = self.download_single(context, save_path=save_path,
|
||||||
channel=channel, source=source,
|
channel=channel, source=source,
|
||||||
userid=userid, username=username)
|
userid=userid, username=username,
|
||||||
|
media_category=media_category)
|
||||||
if download_id:
|
if download_id:
|
||||||
# 下载成功
|
# 下载成功
|
||||||
logger.info(f"{meta.title} 添加下载成功")
|
logger.info(f"{meta.title} 添加下载成功")
|
||||||
@@ -704,7 +713,8 @@ class DownloadChain(ChainBase):
|
|||||||
channel=channel,
|
channel=channel,
|
||||||
source=source,
|
source=source,
|
||||||
userid=userid,
|
userid=userid,
|
||||||
username=username
|
username=username,
|
||||||
|
media_category=media_category
|
||||||
)
|
)
|
||||||
if not download_id:
|
if not download_id:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import json
|
import copy
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from json import JSONDecodeError
|
|
||||||
from typing import Dict, List, Optional, Union, Tuple
|
from typing import Dict, List, Optional, Union, Tuple
|
||||||
|
|
||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
@@ -332,9 +331,11 @@ class SubscribeChain(ChainBase):
|
|||||||
|
|
||||||
# 优先级过滤规则
|
# 优先级过滤规则
|
||||||
if subscribe.best_version:
|
if subscribe.best_version:
|
||||||
rule_groups = self.systemconfig.get(SystemConfigKey.BestVersionFilterRuleGroups)
|
rule_groups = subscribe.filter_groups \
|
||||||
|
or self.systemconfig.get(SystemConfigKey.BestVersionFilterRuleGroups)
|
||||||
else:
|
else:
|
||||||
rule_groups = self.systemconfig.get(SystemConfigKey.SubscribeFilterRuleGroups)
|
rule_groups = subscribe.filter_groups \
|
||||||
|
or self.systemconfig.get(SystemConfigKey.SubscribeFilterRuleGroups)
|
||||||
|
|
||||||
# 搜索,同时电视剧会过滤掉不需要的剧集
|
# 搜索,同时电视剧会过滤掉不需要的剧集
|
||||||
contexts = self.searchchain.process(mediainfo=mediainfo,
|
contexts = self.searchchain.process(mediainfo=mediainfo,
|
||||||
@@ -381,7 +382,8 @@ class SubscribeChain(ChainBase):
|
|||||||
no_exists=no_exists,
|
no_exists=no_exists,
|
||||||
userid=subscribe.username,
|
userid=subscribe.username,
|
||||||
username=subscribe.username,
|
username=subscribe.username,
|
||||||
save_path=subscribe.save_path
|
save_path=subscribe.save_path,
|
||||||
|
media_category=subscribe.media_category
|
||||||
)
|
)
|
||||||
|
|
||||||
# 判断是否应完成订阅
|
# 判断是否应完成订阅
|
||||||
@@ -478,21 +480,17 @@ class SubscribeChain(ChainBase):
|
|||||||
# 如果订阅未指定站点信息,直接返回默认站点
|
# 如果订阅未指定站点信息,直接返回默认站点
|
||||||
if not subscribe.sites:
|
if not subscribe.sites:
|
||||||
return default_sites
|
return default_sites
|
||||||
try:
|
# 尝试解析订阅中的站点数据
|
||||||
# 尝试解析订阅中的站点数据
|
user_sites = subscribe.sites
|
||||||
user_sites = subscribe.sites
|
# 计算 user_sites 和 default_sites 的交集
|
||||||
# 计算 user_sites 和 default_sites 的交集
|
intersection_sites = [site for site in user_sites if site in default_sites]
|
||||||
intersection_sites = [site for site in user_sites if site in default_sites]
|
# 如果交集与原始订阅不一致,更新数据库
|
||||||
# 如果交集与原始订阅不一致,更新数据库
|
if set(intersection_sites) != set(user_sites):
|
||||||
if set(intersection_sites) != set(user_sites):
|
self.subscribeoper.update(subscribe.id, {
|
||||||
self.subscribeoper.update(subscribe.id, {
|
"sites": intersection_sites
|
||||||
"sites": intersection_sites
|
})
|
||||||
})
|
# 如果交集为空,返回默认站点
|
||||||
# 如果交集为空,返回默认站点
|
return intersection_sites if intersection_sites else default_sites
|
||||||
return intersection_sites if intersection_sites else default_sites
|
|
||||||
except JSONDecodeError:
|
|
||||||
# 如果 JSON 解析失败,返回默认站点
|
|
||||||
return default_sites
|
|
||||||
|
|
||||||
def get_subscribed_sites(self) -> Optional[List[int]]:
|
def get_subscribed_sites(self) -> Optional[List[int]]:
|
||||||
"""
|
"""
|
||||||
@@ -521,8 +519,6 @@ class SubscribeChain(ChainBase):
|
|||||||
if not torrents:
|
if not torrents:
|
||||||
logger.warn('没有缓存资源,无法匹配订阅')
|
logger.warn('没有缓存资源,无法匹配订阅')
|
||||||
return
|
return
|
||||||
# 记录重新识别过的种子
|
|
||||||
_recognize_cached = []
|
|
||||||
# 所有订阅
|
# 所有订阅
|
||||||
subscribes = self.subscribeoper.list('R')
|
subscribes = self.subscribeoper.list('R')
|
||||||
# 遍历订阅
|
# 遍历订阅
|
||||||
@@ -541,12 +537,9 @@ class SubscribeChain(ChainBase):
|
|||||||
# 订阅的站点域名列表
|
# 订阅的站点域名列表
|
||||||
domains = []
|
domains = []
|
||||||
if subscribe.sites:
|
if subscribe.sites:
|
||||||
try:
|
domains = self.siteoper.get_domains_by_ids(subscribe.sites)
|
||||||
siteids = subscribe.sites
|
# 自定义识别词
|
||||||
if siteids:
|
custom_words = subscribe.custom_words.split("\n") if subscribe.custom_words else []
|
||||||
domains = self.siteoper.get_domains_by_ids(siteids)
|
|
||||||
except JSONDecodeError:
|
|
||||||
pass
|
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
mediainfo: MediaInfo = self.recognize_media(meta=meta, mtype=meta.type,
|
mediainfo: MediaInfo = self.recognize_media(meta=meta, mtype=meta.type,
|
||||||
tmdbid=subscribe.tmdbid,
|
tmdbid=subscribe.tmdbid,
|
||||||
@@ -612,34 +605,62 @@ class SubscribeChain(ChainBase):
|
|||||||
continue
|
continue
|
||||||
logger.debug(f'开始匹配站点:{domain},共缓存了 {len(contexts)} 个种子...')
|
logger.debug(f'开始匹配站点:{domain},共缓存了 {len(contexts)} 个种子...')
|
||||||
for context in contexts:
|
for context in contexts:
|
||||||
# 检查是否匹配
|
# 提取信息
|
||||||
torrent_meta = context.meta_info
|
torrent_meta = copy.deepcopy(context.meta_info)
|
||||||
torrent_mediainfo = context.media_info
|
torrent_mediainfo = copy.deepcopy(context.media_info)
|
||||||
torrent_info = context.torrent_info
|
torrent_info = context.torrent_info
|
||||||
|
|
||||||
# 先判断是否有没识别的种子
|
# 不在订阅站点范围的不处理
|
||||||
if not torrent_mediainfo or (not torrent_mediainfo.tmdb_id and not torrent_mediainfo.douban_id):
|
sub_sites = self.get_sub_sites(subscribe)
|
||||||
_cache_key = f"{torrent_info.title}_{torrent_info.description}"
|
if sub_sites and torrent_info.site not in sub_sites:
|
||||||
if _cache_key not in _recognize_cached:
|
logger.debug(f"{torrent_info.site_name} - {torrent_info.title} 不符合订阅站点要求")
|
||||||
_recognize_cached.append(_cache_key)
|
continue
|
||||||
|
|
||||||
|
# 匹配订阅参数
|
||||||
|
if not self.torrenthelper.filter_torrent(torrent_info=torrent_info,
|
||||||
|
filter_params=self.get_params(subscribe)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 先判断是否有没识别的种子,有则重新识别;如果订阅有自定义识别词,则不使用预识别的信息
|
||||||
|
if not torrent_mediainfo \
|
||||||
|
or (not torrent_mediainfo.tmdb_id and not torrent_mediainfo.douban_id) \
|
||||||
|
or subscribe.custom_words:
|
||||||
|
if not subscribe.custom_words:
|
||||||
logger.info(
|
logger.info(
|
||||||
f'{torrent_info.site_name} - {torrent_info.title} 订阅缓存为未识别状态,尝试重新识别...')
|
f'{torrent_info.site_name} - {torrent_info.title} 订阅缓存为未识别状态,'
|
||||||
# 重新识别(不使用缓存)
|
f'尝试重新识别媒体信息...')
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
f'{torrent_info.site_name} - {torrent_info.title} 因订阅存在自定义识别词,'
|
||||||
|
f'正在重新识别元数据和媒体信息...')
|
||||||
|
# 重新识别元数据
|
||||||
|
torrent_meta = MetaInfo(title=torrent_info.title, subtitle=torrent_info.description,
|
||||||
|
custom_words=custom_words)
|
||||||
|
# 重新识别媒体信息
|
||||||
|
if subscribe.custom_words:
|
||||||
|
torrent_mediainfo = self.recognize_media(meta=torrent_meta)
|
||||||
|
else:
|
||||||
|
# 不使用识别缓存
|
||||||
torrent_mediainfo = self.recognize_media(meta=torrent_meta, cache=False)
|
torrent_mediainfo = self.recognize_media(meta=torrent_meta, cache=False)
|
||||||
if not torrent_mediainfo:
|
if torrent_mediainfo:
|
||||||
logger.warn(
|
# 更新种子缓存
|
||||||
f'{torrent_info.site_name} - {torrent_info.title} 重新识别失败,尝试通过标题匹配...')
|
context.media_info = torrent_mediainfo
|
||||||
if self.torrenthelper.match_torrent(mediainfo=mediainfo,
|
if not torrent_mediainfo:
|
||||||
torrent_meta=torrent_meta,
|
# 通过标题匹配兜底
|
||||||
torrent=torrent_info):
|
logger.warn(
|
||||||
# 匹配成功
|
f'{torrent_info.site_name} - {torrent_info.title} 重新识别失败,尝试通过标题匹配...')
|
||||||
logger.info(
|
if self.torrenthelper.match_torrent(mediainfo=mediainfo,
|
||||||
f'{mediainfo.title_year} 通过标题匹配到资源:{torrent_info.site_name} - {torrent_info.title}')
|
torrent_meta=torrent_meta,
|
||||||
# 更新缓存
|
torrent=torrent_info):
|
||||||
|
# 匹配成功
|
||||||
|
logger.info(
|
||||||
|
f'{mediainfo.title_year} 通过标题匹配到可用资源:{torrent_info.site_name} - {torrent_info.title}')
|
||||||
|
if not subscribe.custom_words:
|
||||||
|
# 更新种子缓存
|
||||||
torrent_mediainfo = mediainfo
|
torrent_mediainfo = mediainfo
|
||||||
context.media_info = mediainfo
|
context.media_info = mediainfo
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 直接比对媒体信息
|
# 直接比对媒体信息
|
||||||
if torrent_mediainfo and (torrent_mediainfo.tmdb_id or torrent_mediainfo.douban_id):
|
if torrent_mediainfo and (torrent_mediainfo.tmdb_id or torrent_mediainfo.douban_id):
|
||||||
@@ -652,30 +673,10 @@ class SubscribeChain(ChainBase):
|
|||||||
and torrent_mediainfo.douban_id != mediainfo.douban_id:
|
and torrent_mediainfo.douban_id != mediainfo.douban_id:
|
||||||
continue
|
continue
|
||||||
logger.info(
|
logger.info(
|
||||||
f'{mediainfo.title_year} 通过媒体信ID匹配到资源:{torrent_info.site_name} - {torrent_info.title}')
|
f'{mediainfo.title_year} 通过媒体信ID匹配到可用资源:{torrent_info.site_name} - {torrent_info.title}')
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 过滤规则
|
|
||||||
if subscribe.best_version:
|
|
||||||
rule_groups = self.systemconfig.get(SystemConfigKey.BestVersionFilterRuleGroups)
|
|
||||||
else:
|
|
||||||
rule_groups = self.systemconfig.get(SystemConfigKey.SubscribeFilterRuleGroups)
|
|
||||||
result: List[TorrentInfo] = self.filter_torrents(
|
|
||||||
rule_groups=rule_groups,
|
|
||||||
torrent_list=[torrent_info],
|
|
||||||
mediainfo=torrent_mediainfo)
|
|
||||||
if result is not None and not result:
|
|
||||||
# 不符合过滤规则
|
|
||||||
logger.debug(f"{torrent_info.title} 不匹配过滤规则")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 不在订阅站点范围的不处理
|
|
||||||
sub_sites = self.get_sub_sites(subscribe)
|
|
||||||
if sub_sites and torrent_info.site not in sub_sites:
|
|
||||||
logger.debug(f"{torrent_info.site_name} - {torrent_info.title} 不符合订阅站点要求")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 如果是电视剧
|
# 如果是电视剧
|
||||||
if torrent_mediainfo.type == MediaType.TV:
|
if torrent_mediainfo.type == MediaType.TV:
|
||||||
# 有多季的不要
|
# 有多季的不要
|
||||||
@@ -714,6 +715,22 @@ class SubscribeChain(ChainBase):
|
|||||||
logger.debug(f'{subscribe.name} 正在洗版,{torrent_info.title} 不是整季')
|
logger.debug(f'{subscribe.name} 正在洗版,{torrent_info.title} 不是整季')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# 优先级过滤规则
|
||||||
|
if subscribe.best_version:
|
||||||
|
rule_groups = subscribe.filter_groups \
|
||||||
|
or self.systemconfig.get(SystemConfigKey.BestVersionFilterRuleGroups)
|
||||||
|
else:
|
||||||
|
rule_groups = subscribe.filter_groups \
|
||||||
|
or self.systemconfig.get(SystemConfigKey.SubscribeFilterRuleGroups)
|
||||||
|
result: List[TorrentInfo] = self.filter_torrents(
|
||||||
|
rule_groups=rule_groups,
|
||||||
|
torrent_list=[torrent_info],
|
||||||
|
mediainfo=torrent_mediainfo)
|
||||||
|
if result is not None and not result:
|
||||||
|
# 不符合过滤规则
|
||||||
|
logger.debug(f"{torrent_info.title} 不匹配过滤规则")
|
||||||
|
continue
|
||||||
|
|
||||||
# 洗版时,优先级小于已下载优先级的不要
|
# 洗版时,优先级小于已下载优先级的不要
|
||||||
if subscribe.best_version:
|
if subscribe.best_version:
|
||||||
if subscribe.current_priority \
|
if subscribe.current_priority \
|
||||||
@@ -721,11 +738,6 @@ class SubscribeChain(ChainBase):
|
|||||||
logger.info(f'{subscribe.name} 正在洗版,{torrent_info.title} 优先级低于或等于已下载优先级')
|
logger.info(f'{subscribe.name} 正在洗版,{torrent_info.title} 优先级低于或等于已下载优先级')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 匹配订阅参数
|
|
||||||
if not self.torrenthelper.filter_torrent(torrent_info=torrent_info,
|
|
||||||
filter_params=self.get_params(subscribe)):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 匹配成功
|
# 匹配成功
|
||||||
logger.info(f'{mediainfo.title_year} 匹配成功:{torrent_info.title}')
|
logger.info(f'{mediainfo.title_year} 匹配成功:{torrent_info.title}')
|
||||||
_match_context.append(context)
|
_match_context.append(context)
|
||||||
@@ -743,7 +755,8 @@ class SubscribeChain(ChainBase):
|
|||||||
no_exists=no_exists,
|
no_exists=no_exists,
|
||||||
userid=subscribe.username,
|
userid=subscribe.username,
|
||||||
username=subscribe.username,
|
username=subscribe.username,
|
||||||
save_path=subscribe.save_path)
|
save_path=subscribe.save_path,
|
||||||
|
media_category=subscribe.media_category)
|
||||||
# 判断是否要完成订阅
|
# 判断是否要完成订阅
|
||||||
self.finish_subscribe_or_not(subscribe=subscribe, meta=meta, mediainfo=mediainfo,
|
self.finish_subscribe_or_not(subscribe=subscribe, meta=meta, mediainfo=mediainfo,
|
||||||
downloads=downloads, lefts=lefts)
|
downloads=downloads, lefts=lefts)
|
||||||
@@ -1191,8 +1204,6 @@ class SubscribeChain(ChainBase):
|
|||||||
download_his = self.downloadhis.get_by_mediaid(tmdbid=subscribe.tmdbid, doubanid=subscribe.doubanid)
|
download_his = self.downloadhis.get_by_mediaid(tmdbid=subscribe.tmdbid, doubanid=subscribe.doubanid)
|
||||||
if download_his:
|
if download_his:
|
||||||
for his in download_his:
|
for his in download_his:
|
||||||
# 种子链接
|
|
||||||
torrent_url = f"【{his.torrent_site}】{his.torrent_name}"
|
|
||||||
# 查询下载文件
|
# 查询下载文件
|
||||||
files = self.downloadhis.get_files_by_hash(his.hash)
|
files = self.downloadhis.get_files_by_hash(his.hash)
|
||||||
if files:
|
if files:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import json
|
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -118,6 +117,9 @@ class TransferChain(ChainBase):
|
|||||||
if mediainfo:
|
if mediainfo:
|
||||||
# 补充图片
|
# 补充图片
|
||||||
self.obtain_images(mediainfo)
|
self.obtain_images(mediainfo)
|
||||||
|
# 更新自定义媒体类别
|
||||||
|
if downloadhis.media_category:
|
||||||
|
mediainfo.category = downloadhis.media_category
|
||||||
else:
|
else:
|
||||||
# 非MoviePilot下载的任务,按文件识别
|
# 非MoviePilot下载的任务,按文件识别
|
||||||
mediainfo = None
|
mediainfo = None
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class WordsMatcher(metaclass=Singleton):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.systemconfig = SystemConfigOper()
|
self.systemconfig = SystemConfigOper()
|
||||||
|
|
||||||
def prepare(self, title: str) -> Tuple[str, List[str]]:
|
def prepare(self, title: str, custom_words: List[str] = None) -> Tuple[str, List[str]]:
|
||||||
"""
|
"""
|
||||||
预处理标题,支持三种格式
|
预处理标题,支持三种格式
|
||||||
1:屏蔽词
|
1:屏蔽词
|
||||||
@@ -23,7 +23,7 @@ class WordsMatcher(metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
appley_words = []
|
appley_words = []
|
||||||
# 读取自定义识别词
|
# 读取自定义识别词
|
||||||
words: List[str] = self.systemconfig.get(SystemConfigKey.CustomIdentifiers) or []
|
words: List[str] = custom_words or self.systemconfig.get(SystemConfigKey.CustomIdentifiers) or []
|
||||||
for word in words:
|
for word in words:
|
||||||
if not word or word.startswith("#"):
|
if not word or word.startswith("#"):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Tuple
|
from typing import Tuple, List
|
||||||
|
|
||||||
import regex as re
|
import regex as re
|
||||||
|
|
||||||
@@ -10,17 +10,18 @@ from app.log import logger
|
|||||||
from app.schemas.types import MediaType
|
from app.schemas.types import MediaType
|
||||||
|
|
||||||
|
|
||||||
def MetaInfo(title: str, subtitle: str = None) -> MetaBase:
|
def MetaInfo(title: str, subtitle: str = None, custom_words: List[str] = None) -> MetaBase:
|
||||||
"""
|
"""
|
||||||
根据标题和副标题识别元数据
|
根据标题和副标题识别元数据
|
||||||
:param title: 标题、种子名、文件名
|
:param title: 标题、种子名、文件名
|
||||||
:param subtitle: 副标题、描述
|
:param subtitle: 副标题、描述
|
||||||
|
:param custom_words: 自定义识别词列表
|
||||||
:return: MetaAnime、MetaVideo
|
:return: MetaAnime、MetaVideo
|
||||||
"""
|
"""
|
||||||
# 原标题
|
# 原标题
|
||||||
org_title = title
|
org_title = title
|
||||||
# 预处理标题
|
# 预处理标题
|
||||||
title, apply_words = WordsMatcher().prepare(title)
|
title, apply_words = WordsMatcher().prepare(title, custom_words=custom_words)
|
||||||
# 获取标题中媒体信息
|
# 获取标题中媒体信息
|
||||||
title, metainfo = find_metainfo(title)
|
title, metainfo = find_metainfo(title)
|
||||||
# 判断是否处理文件
|
# 判断是否处理文件
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import json
|
|
||||||
from typing import Any, Self, List, Tuple, Optional, Generator
|
from typing import Any, Self, List, Tuple, Optional, Generator
|
||||||
|
|
||||||
from sqlalchemy import create_engine, QueuePool, and_, inspect
|
from sqlalchemy import create_engine, QueuePool, and_, inspect
|
||||||
from sqlalchemy.orm import declared_attr, sessionmaker, Session, scoped_session, as_declarative
|
from sqlalchemy.orm import declared_attr, sessionmaker, Session, scoped_session, as_declarative
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.utils.object import ObjectUtils
|
|
||||||
|
|
||||||
# 数据库引擎
|
# 数据库引擎
|
||||||
Engine = create_engine(
|
Engine = create_engine(
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ class DownloadHistory(Base):
|
|||||||
date = Column(String)
|
date = Column(String)
|
||||||
# 附加信息
|
# 附加信息
|
||||||
note = Column(JSON)
|
note = Column(JSON)
|
||||||
|
# 自定义媒体类别
|
||||||
|
media_category = Column(String)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@db_query
|
@db_query
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ class Subscribe(Base):
|
|||||||
search_imdbid = Column(Integer, default=0)
|
search_imdbid = Column(Integer, default=0)
|
||||||
# 是否手动修改过总集数 0否 1是
|
# 是否手动修改过总集数 0否 1是
|
||||||
manual_total_episode = Column(Integer, default=0)
|
manual_total_episode = Column(Integer, default=0)
|
||||||
|
# 自定义识别词
|
||||||
|
custom_words = Column(String)
|
||||||
|
# 自定义媒体类别
|
||||||
|
media_category = Column(String)
|
||||||
|
# 过滤规则组
|
||||||
|
filter_groups = Column(JSON, default=list)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@db_query
|
@db_query
|
||||||
|
|||||||
@@ -326,10 +326,16 @@ class Monitor(metaclass=Singleton):
|
|||||||
download_hash = download_history.download_hash
|
download_hash = download_history.download_hash
|
||||||
|
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
if download_history and download_history.tmdbid:
|
if download_history and (download_history.tmdbid or download_history.doubanid):
|
||||||
|
# 下载记录中已存在识别信息
|
||||||
mediainfo: MediaInfo = self.mediaChain.recognize_media(mtype=MediaType(download_history.type),
|
mediainfo: MediaInfo = self.mediaChain.recognize_media(mtype=MediaType(download_history.type),
|
||||||
tmdbid=download_history.tmdbid,
|
tmdbid=download_history.tmdbid,
|
||||||
doubanid=download_history.doubanid)
|
doubanid=download_history.doubanid)
|
||||||
|
if mediainfo:
|
||||||
|
# 更新自定义媒体类别
|
||||||
|
if download_history.media_category:
|
||||||
|
mediainfo.category = download_history.media_category
|
||||||
|
|
||||||
else:
|
else:
|
||||||
mediainfo: MediaInfo = self.mediaChain.recognize_by_meta(file_meta)
|
mediainfo: MediaInfo = self.mediaChain.recognize_by_meta(file_meta)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import json
|
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class Subscribe(BaseModel):
|
class Subscribe(BaseModel):
|
||||||
@@ -65,17 +64,12 @@ class Subscribe(BaseModel):
|
|||||||
search_imdbid: Optional[int] = 0
|
search_imdbid: Optional[int] = 0
|
||||||
# 时间
|
# 时间
|
||||||
date: Optional[str] = None
|
date: Optional[str] = None
|
||||||
|
# 自定义识别词
|
||||||
@validator('sites', pre=True)
|
custom_words: Optional[str] = None
|
||||||
def parse_json_fields(cls, value):
|
# 自定义媒体类别
|
||||||
if value:
|
media_category: Optional[str] = None
|
||||||
if isinstance(value, str):
|
# 过滤规则组
|
||||||
try:
|
filter_groups: Optional[List[str]] = []
|
||||||
return json.loads(value)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
return []
|
|
||||||
return value
|
|
||||||
return []
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import json
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
# Shared properties
|
# Shared properties
|
||||||
@@ -23,17 +22,6 @@ class UserBase(BaseModel):
|
|||||||
# 个性化设置
|
# 个性化设置
|
||||||
settings: Optional[dict] = {}
|
settings: Optional[dict] = {}
|
||||||
|
|
||||||
@validator('permissions', 'settings', pre=True)
|
|
||||||
def parse_json_fields(cls, value):
|
|
||||||
if value:
|
|
||||||
if isinstance(value, str):
|
|
||||||
try:
|
|
||||||
return json.loads(value)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
return {}
|
|
||||||
return value
|
|
||||||
return {}
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
# 站点数据统计增加站点名称
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
with op.batch_alter_table("siteuserdata") as batch_op:
|
op.add_column('siteuserdata', sa.Column('name', sa.String(), nullable=True))
|
||||||
batch_op.add_column(sa.Column('name', sa.VARCHAR))
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
49
database/versions/e2dbe1421fa4_2_0_3.py
Normal file
49
database/versions/e2dbe1421fa4_2_0_3.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""2.0.3
|
||||||
|
|
||||||
|
Revision ID: e2dbe1421fa4
|
||||||
|
Revises: 0fb94bf69b38
|
||||||
|
Create Date: 2024-10-09 13:44:13.926529
|
||||||
|
|
||||||
|
"""
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from app.db import SessionFactory
|
||||||
|
from app.db.models import UserConfig
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'e2dbe1421fa4'
|
||||||
|
down_revision = '0fb94bf69b38'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
# 支持订阅自定义媒体类别和过滤规则组、自定义识别词
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
op.add_column('downloadhistory', sa.Column('media_category', sa.String(), nullable=True))
|
||||||
|
op.add_column('subscribe', sa.Column('custom_words', sa.String(), nullable=True))
|
||||||
|
op.add_column('subscribe', sa.Column('media_category', sa.String(), nullable=True))
|
||||||
|
op.add_column('subscribe', sa.Column('filter_groups', sa.JSON(), nullable=True))
|
||||||
|
# 将String转换为JSON类型
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
op.alter_column('subscribe', 'note', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
op.alter_column('downloadhistory', 'note', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
op.alter_column('mediaserveritem', 'note', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
op.alter_column('message', 'note', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
op.alter_column('plugindata', 'value', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
op.alter_column('site', 'note', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
op.alter_column('sitestatistic', 'note', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
op.alter_column('systemconfig', 'value', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
op.alter_column('userconfig', 'value', existing_type=sa.String(), type_=sa.JSON())
|
||||||
|
# 清空用户配置表中不兼容的数据
|
||||||
|
with SessionFactory() as db:
|
||||||
|
UserConfig.truncate(db)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user