mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-14 07:26:50 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1ab19f3cf | ||
|
|
beebfb2e19 | ||
|
|
cfca90aa7d | ||
|
|
19fe0a32c8 | ||
|
|
76659f8837 | ||
|
|
2254715190 | ||
|
|
ae1a5460d4 | ||
|
|
27d9f910ff | ||
|
|
28db4881d7 | ||
|
|
7c76c3ccd6 | ||
|
|
007bd24374 | ||
|
|
c8dc30287c | ||
|
|
360184bbd1 | ||
|
|
e8ed2454a1 | ||
|
|
923ecf29b8 | ||
|
|
a8f8bf5872 | ||
|
|
bedcd94020 | ||
|
|
959d4da1f8 | ||
|
|
861453c1a8 | ||
|
|
2f4072da0d | ||
|
|
411b5e0ca6 | ||
|
|
3f03963811 | ||
|
|
d43f81e118 | ||
|
|
b97dbd2515 | ||
|
|
c6a20a9ed3 | ||
|
|
27f0f29eef | ||
|
|
223508ae72 | ||
|
|
bce0a4b8cd |
@@ -40,54 +40,67 @@ class FetchMediasAction(BaseAction):
|
||||
{
|
||||
"func": RecommendChain().tmdb_trending,
|
||||
"name": '流行趋势',
|
||||
"api_path": "recommend/tmdb_trending"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_movie_showing,
|
||||
"name": '正在热映',
|
||||
"api_path": "recommend/douban_showing"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().bangumi_calendar,
|
||||
"name": 'Bangumi每日放送',
|
||||
"api_path": "recommend/bangumi_calendar"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().tmdb_movies,
|
||||
"name": 'TMDB热门电影',
|
||||
"api_path": "recommend/tmdb_movies"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().tmdb_tvs,
|
||||
"name": 'TMDB热门电视剧',
|
||||
"api_path": "recommend/tmdb_tvs?with_original_language=zh|en|ja|ko"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_movie_hot,
|
||||
"name": '豆瓣热门电影',
|
||||
"api_path": "recommend/douban_movie_hot"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_tv_hot,
|
||||
"name": '豆瓣热门电视剧',
|
||||
"api_path": "recommend/douban_tv_hot"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_tv_animation,
|
||||
"name": '豆瓣热门动漫',
|
||||
"api_path": "recommend/douban_tv_animation"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_movies,
|
||||
"name": '豆瓣最新电影',
|
||||
"api_path": "recommend/douban_movies"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_tvs,
|
||||
"name": '豆瓣最新电视剧',
|
||||
"api_path": "recommend/douban_tvs"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_movie_top250,
|
||||
"name": '豆瓣电影TOP250',
|
||||
"api_path": "recommend/douban_movie_top250"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_tv_weekly_chinese,
|
||||
"name": '豆瓣国产剧集榜',
|
||||
"api_path": "recommend/douban_tv_weekly_chinese"
|
||||
},
|
||||
{
|
||||
"func": RecommendChain().douban_tv_weekly_global,
|
||||
"name": '豆瓣全球剧集榜',
|
||||
"api_path": "recommend/douban_tv_weekly_global"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -124,7 +137,7 @@ class FetchMediasAction(BaseAction):
|
||||
获取数据源
|
||||
"""
|
||||
for s in self.__inner_sources:
|
||||
if s['name'] == source:
|
||||
if s['api_path'] == source:
|
||||
return s
|
||||
return None
|
||||
|
||||
@@ -135,13 +148,14 @@ class FetchMediasAction(BaseAction):
|
||||
params = FetchMediasParams(**params)
|
||||
try:
|
||||
if params.source_type == "ranking":
|
||||
for name in params.sources:
|
||||
for api_path in params.sources:
|
||||
if global_vars.is_workflow_stopped(workflow_id):
|
||||
break
|
||||
source = self.__get_source(name)
|
||||
source = self.__get_source(api_path)
|
||||
if not source:
|
||||
continue
|
||||
logger.info(f"获取媒体数据 {source} ...")
|
||||
name = source.get("name")
|
||||
results = []
|
||||
if source.get("func"):
|
||||
results = source['func']()
|
||||
|
||||
@@ -67,7 +67,7 @@ class ChainBase(metaclass=ABCMeta):
|
||||
"""
|
||||
try:
|
||||
with open(settings.TEMP_PATH / filename, 'wb') as f:
|
||||
pickle.dump(cache, f) # noqa
|
||||
pickle.dump(cache, f) # noqa
|
||||
except Exception as err:
|
||||
logger.error(f"保存缓存 {filename} 出错:{str(err)}")
|
||||
finally:
|
||||
@@ -374,7 +374,7 @@ class ChainBase(metaclass=ABCMeta):
|
||||
return self.run_module("search_torrents", site=site, keywords=keywords,
|
||||
mtype=mtype, page=page)
|
||||
|
||||
def refresh_torrents(self, site: dict, keyword: Optional[str] = None,
|
||||
def refresh_torrents(self, site: dict, keyword: Optional[str] = None,
|
||||
cat: Optional[str] = None, page: Optional[int] = 0) -> List[TorrentInfo]:
|
||||
"""
|
||||
获取站点最新一页的种子,多个站点需要多线程处理
|
||||
@@ -543,12 +543,12 @@ class ChainBase(metaclass=ABCMeta):
|
||||
return self.run_module("media_files", mediainfo=mediainfo)
|
||||
|
||||
def post_message(self,
|
||||
message: Optional[Notification] = None,
|
||||
meta: Optional[MetaBase] = None,
|
||||
mediainfo: Optional[MediaInfo] = None,
|
||||
torrentinfo: Optional[TorrentInfo] = None,
|
||||
transferinfo: Optional[TransferInfo] = None,
|
||||
**kwargs) -> None:
|
||||
message: Optional[Notification] = None,
|
||||
meta: Optional[MetaBase] = None,
|
||||
mediainfo: Optional[MediaInfo] = None,
|
||||
torrentinfo: Optional[TorrentInfo] = None,
|
||||
transferinfo: Optional[TransferInfo] = None,
|
||||
**kwargs) -> None:
|
||||
"""
|
||||
发送消息
|
||||
:param message: Notification实例
|
||||
@@ -561,7 +561,7 @@ class ChainBase(metaclass=ABCMeta):
|
||||
"""
|
||||
# 渲染消息
|
||||
message = MessageTemplateHelper.render(message=message, meta=meta, mediainfo=mediainfo,
|
||||
torrentinfo=torrentinfo, transferinfo=transferinfo, **kwargs)
|
||||
torrentinfo=torrentinfo, transferinfo=transferinfo, **kwargs)
|
||||
# 保存消息
|
||||
self.messagehelper.put(message, role="user", title=message.title)
|
||||
self.messageoper.add(**message.dict())
|
||||
@@ -643,7 +643,7 @@ class ChainBase(metaclass=ABCMeta):
|
||||
self.messageoper.add(**message.dict(), note=note_list)
|
||||
return self.messagequeue.send_message("post_torrents_message", message=message, torrents=torrents)
|
||||
|
||||
def metadata_img(self, mediainfo: MediaInfo,
|
||||
def metadata_img(self, mediainfo: MediaInfo,
|
||||
season: Optional[int] = None, episode: Optional[int] = None) -> Optional[dict]:
|
||||
"""
|
||||
获取图片名称和url
|
||||
|
||||
@@ -422,13 +422,17 @@ class MessageChain(ChainBase):
|
||||
or text.find("继续") != -1:
|
||||
# 聊天
|
||||
content = text
|
||||
action = "chat"
|
||||
action = "Chat"
|
||||
elif StringUtils.is_link(text):
|
||||
# 链接
|
||||
content = text
|
||||
action = "Link"
|
||||
else:
|
||||
# 搜索
|
||||
content = text
|
||||
action = "Search"
|
||||
|
||||
if action != "chat":
|
||||
if action in ["Search", "ReSearch", "Subscribe", "ReSubscribe"]:
|
||||
# 搜索
|
||||
meta, medias = self.mediachain.search(content)
|
||||
# 识别
|
||||
|
||||
@@ -561,6 +561,26 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
||||
logger.debug(f"match lock acquired at {datetime.now()}")
|
||||
# 所有订阅
|
||||
subscribes = self.subscribeoper.list(self.get_states_for_search('R'))
|
||||
|
||||
# 预识别所有未识别的种子
|
||||
processed_torrents = {}
|
||||
for domain, contexts in torrents.items():
|
||||
processed_torrents[domain] = []
|
||||
for context in contexts:
|
||||
# 复制上下文避免修改原始数据
|
||||
_context = copy.deepcopy(context)
|
||||
torrent_meta = _context.meta_info
|
||||
torrent_mediainfo = _context.media_info
|
||||
|
||||
# 如果种子未识别,尝试识别
|
||||
if not torrent_mediainfo or (not torrent_mediainfo.tmdb_id and not torrent_mediainfo.douban_id):
|
||||
torrent_mediainfo = self.recognize_media(meta=torrent_meta)
|
||||
if torrent_mediainfo:
|
||||
# 更新种子缓存
|
||||
context.media_info = torrent_mediainfo
|
||||
# 添加已预处理
|
||||
processed_torrents[domain].append(_context)
|
||||
|
||||
# 遍历订阅
|
||||
for subscribe in subscribes:
|
||||
if global_vars.is_system_stopped:
|
||||
@@ -604,9 +624,9 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
||||
else:
|
||||
custom_words_list = None
|
||||
|
||||
# 遍历缓存种子
|
||||
# 遍历预识别后的种子
|
||||
_match_context = []
|
||||
for domain, contexts in torrents.items():
|
||||
for domain, contexts in processed_torrents.items():
|
||||
if global_vars.is_system_stopped:
|
||||
break
|
||||
if domains and domain not in domains:
|
||||
@@ -638,32 +658,28 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
||||
custom_words=custom_words_list)
|
||||
# 更新元数据缓存
|
||||
context.meta_info = torrent_meta
|
||||
# 媒体信息需要重新识别
|
||||
torrent_mediainfo = None
|
||||
|
||||
# 先判断是否有没识别的种子,否则重新识别
|
||||
if not torrent_mediainfo \
|
||||
or (not torrent_mediainfo.tmdb_id and not torrent_mediainfo.douban_id):
|
||||
# 重新识别媒体信息
|
||||
torrent_mediainfo = self.recognize_media(meta=torrent_meta,
|
||||
episode_group=subscribe.episode_group)
|
||||
if torrent_mediainfo:
|
||||
# 更新种子缓存
|
||||
context.media_info = torrent_mediainfo
|
||||
else:
|
||||
# 通过标题匹配兜底
|
||||
logger.warn(
|
||||
f'{torrent_info.site_name} - {torrent_info.title} 重新识别失败,尝试通过标题匹配...')
|
||||
if self.torrenthelper.match_torrent(mediainfo=mediainfo,
|
||||
torrent_meta=torrent_meta,
|
||||
torrent=torrent_info):
|
||||
# 匹配成功
|
||||
logger.info(
|
||||
f'{mediainfo.title_year} 通过标题匹配到可选资源:{torrent_info.site_name} - {torrent_info.title}')
|
||||
torrent_mediainfo = mediainfo
|
||||
# 重新识别媒体信息
|
||||
torrent_mediainfo = self.recognize_media(meta=torrent_meta,
|
||||
episode_group=subscribe.episode_group)
|
||||
if torrent_mediainfo:
|
||||
# 更新种子缓存
|
||||
context.media_info = torrent_mediainfo
|
||||
else:
|
||||
continue
|
||||
|
||||
# 如果仍然没有识别到媒体信息,尝试标题匹配
|
||||
if not torrent_mediainfo or (not torrent_mediainfo.tmdb_id and not torrent_mediainfo.douban_id):
|
||||
logger.warn(
|
||||
f'{torrent_info.site_name} - {torrent_info.title} 重新识别失败,尝试通过标题匹配...')
|
||||
if self.torrenthelper.match_torrent(mediainfo=mediainfo,
|
||||
torrent_meta=torrent_meta,
|
||||
torrent=torrent_info):
|
||||
# 匹配成功
|
||||
logger.info(
|
||||
f'{mediainfo.title_year} 通过标题匹配到可选资源:{torrent_info.site_name} - {torrent_info.title}')
|
||||
torrent_mediainfo = mediainfo
|
||||
# 更新种子缓存
|
||||
context.media_info = mediainfo
|
||||
else:
|
||||
continue
|
||||
|
||||
# 直接比对媒体信息
|
||||
if torrent_mediainfo and (torrent_mediainfo.tmdb_id or torrent_mediainfo.douban_id):
|
||||
|
||||
@@ -582,6 +582,12 @@ class MetaBase(object):
|
||||
# Part
|
||||
if not self.part:
|
||||
self.part = meta.part
|
||||
# tmdbid
|
||||
if not self.tmdbid and meta.tmdbid:
|
||||
self.tmdbid = meta.tmdbid
|
||||
# doubanid
|
||||
if not self.doubanid and meta.doubanid:
|
||||
self.doubanid = meta.doubanid
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
|
||||
@@ -31,7 +31,7 @@ class MetaVideo(MetaBase):
|
||||
_part_re = r"(^PART[0-9ABI]{0,2}$|^CD[0-9]{0,2}$|^DVD[0-9]{0,2}$|^DISK[0-9]{0,2}$|^DISC[0-9]{0,2}$)"
|
||||
_roman_numerals = r"^(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"
|
||||
_source_re = r"^BLURAY$|^HDTV$|^UHDTV$|^HDDVD$|^WEBRIP$|^DVDRIP$|^BDRIP$|^BLU$|^WEB$|^BD$|^HDRip$|^REMUX$|^UHD$"
|
||||
_effect_re = r"^SDR$|^HDR\d*$|^DOLBY$|^DOVI$|^DV$|^3D$|^REPACK$|^HLG$"
|
||||
_effect_re = r"^SDR$|^HDR\d*$|^DOLBY$|^DOVI$|^DV$|^3D$|^REPACK$|^HLG$|^HDR10(\+|Plus)$"
|
||||
_resources_type_re = r"%s|%s" % (_source_re, _effect_re)
|
||||
_name_no_begin_re = r"^[\[【].+?[\]】]"
|
||||
_name_no_chinese_re = r".*版|.*字幕"
|
||||
@@ -592,12 +592,12 @@ class MetaVideo(MetaBase):
|
||||
self._stop_name_flag = True
|
||||
self._last_token_type = "videoencode"
|
||||
if not self.video_encode:
|
||||
if re_res.group(1):
|
||||
self.video_encode = re_res.group(1).upper()
|
||||
elif re_res.group(2):
|
||||
self.video_encode = re_res.group(2).lower()
|
||||
if re_res.group(2):
|
||||
self.video_encode = re_res.group(2).upper()
|
||||
elif re_res.group(3):
|
||||
self.video_encode = re_res.group(3).lower()
|
||||
else:
|
||||
self.video_encode = re_res.group(0).upper()
|
||||
self.video_encode = re_res.group(1).upper()
|
||||
self._last_token = self.video_encode
|
||||
elif self.video_encode == "10bit":
|
||||
self.video_encode = f"{re_res.group(1).upper()} 10bit"
|
||||
|
||||
@@ -120,41 +120,69 @@ def find_metainfo(title: str) -> Tuple[str, dict]:
|
||||
return title, metainfo
|
||||
# 从标题中提取媒体信息 格式为{[tmdbid=xxx;type=xxx;s=xxx;e=xxx]}
|
||||
results = re.findall(r'(?<={\[)[\W\w]+(?=]})', title)
|
||||
if not results:
|
||||
return title, metainfo
|
||||
for result in results:
|
||||
# 查找tmdbid信息
|
||||
tmdbid = re.findall(r'(?<=tmdbid=)\d+', result)
|
||||
if tmdbid and tmdbid[0].isdigit():
|
||||
metainfo['tmdbid'] = tmdbid[0]
|
||||
# 查找豆瓣id信息
|
||||
doubanid = re.findall(r'(?<=doubanid=)\d+', result)
|
||||
if doubanid and doubanid[0].isdigit():
|
||||
metainfo['doubanid'] = doubanid[0]
|
||||
# 查找媒体类型
|
||||
mtype = re.findall(r'(?<=type=)\w+', result)
|
||||
if mtype:
|
||||
if mtype[0] == "movies":
|
||||
metainfo['type'] = MediaType.MOVIE
|
||||
elif mtype[0] == "tv":
|
||||
metainfo['type'] = MediaType.TV
|
||||
# 查找季信息
|
||||
begin_season = re.findall(r'(?<=s=)\d+', result)
|
||||
if begin_season and begin_season[0].isdigit():
|
||||
metainfo['begin_season'] = int(begin_season[0])
|
||||
end_season = re.findall(r'(?<=s=\d+-)\d+', result)
|
||||
if end_season and end_season[0].isdigit():
|
||||
metainfo['end_season'] = int(end_season[0])
|
||||
# 查找集信息
|
||||
begin_episode = re.findall(r'(?<=e=)\d+', result)
|
||||
if begin_episode and begin_episode[0].isdigit():
|
||||
metainfo['begin_episode'] = int(begin_episode[0])
|
||||
end_episode = re.findall(r'(?<=e=\d+-)\d+', result)
|
||||
if end_episode and end_episode[0].isdigit():
|
||||
metainfo['end_episode'] = int(end_episode[0])
|
||||
# 去除title中该部分
|
||||
if tmdbid or mtype or begin_season or end_season or begin_episode or end_episode:
|
||||
title = title.replace(f"{{[{result}]}}", '')
|
||||
if results:
|
||||
for result in results:
|
||||
# 查找tmdbid信息
|
||||
tmdbid = re.findall(r'(?<=tmdbid=)\d+', result)
|
||||
if tmdbid and tmdbid[0].isdigit():
|
||||
metainfo['tmdbid'] = tmdbid[0]
|
||||
# 查找豆瓣id信息
|
||||
doubanid = re.findall(r'(?<=doubanid=)\d+', result)
|
||||
if doubanid and doubanid[0].isdigit():
|
||||
metainfo['doubanid'] = doubanid[0]
|
||||
# 查找媒体类型
|
||||
mtype = re.findall(r'(?<=type=)\w+', result)
|
||||
if mtype:
|
||||
if mtype[0] == "movies":
|
||||
metainfo['type'] = MediaType.MOVIE
|
||||
elif mtype[0] == "tv":
|
||||
metainfo['type'] = MediaType.TV
|
||||
# 查找季信息
|
||||
begin_season = re.findall(r'(?<=s=)\d+', result)
|
||||
if begin_season and begin_season[0].isdigit():
|
||||
metainfo['begin_season'] = int(begin_season[0])
|
||||
end_season = re.findall(r'(?<=s=\d+-)\d+', result)
|
||||
if end_season and end_season[0].isdigit():
|
||||
metainfo['end_season'] = int(end_season[0])
|
||||
# 查找集信息
|
||||
begin_episode = re.findall(r'(?<=e=)\d+', result)
|
||||
if begin_episode and begin_episode[0].isdigit():
|
||||
metainfo['begin_episode'] = int(begin_episode[0])
|
||||
end_episode = re.findall(r'(?<=e=\d+-)\d+', result)
|
||||
if end_episode and end_episode[0].isdigit():
|
||||
metainfo['end_episode'] = int(end_episode[0])
|
||||
# 去除title中该部分
|
||||
if tmdbid or mtype or begin_season or end_season or begin_episode or end_episode:
|
||||
title = title.replace(f"{{[{result}]}}", '')
|
||||
|
||||
# 支持Emby格式的ID标签
|
||||
# 1. [tmdbid=xxxx] 或 [tmdbid-xxxx] 格式
|
||||
tmdb_match = re.search(r'\[tmdbid[=\-](\d+)\]', title)
|
||||
if tmdb_match:
|
||||
metainfo['tmdbid'] = tmdb_match.group(1)
|
||||
title = re.sub(r'\[tmdbid[=\-](\d+)\]', '', title).strip()
|
||||
|
||||
# 2. [tmdb=xxxx] 或 [tmdb-xxxx] 格式
|
||||
if not metainfo['tmdbid']:
|
||||
tmdb_match = re.search(r'\[tmdb[=\-](\d+)\]', title)
|
||||
if tmdb_match:
|
||||
metainfo['tmdbid'] = tmdb_match.group(1)
|
||||
title = re.sub(r'\[tmdb[=\-](\d+)\]', '', title).strip()
|
||||
|
||||
# 3. {tmdbid=xxxx} 或 {tmdbid-xxxx} 格式
|
||||
if not metainfo['tmdbid']:
|
||||
tmdb_match = re.search(r'\{tmdbid[=\-](\d+)\}', title)
|
||||
if tmdb_match:
|
||||
metainfo['tmdbid'] = tmdb_match.group(1)
|
||||
title = re.sub(r'\{tmdbid[=\-](\d+)\}', '', title).strip()
|
||||
|
||||
# 4. {tmdb=xxxx} 或 {tmdb-xxxx} 格式
|
||||
if not metainfo['tmdbid']:
|
||||
tmdb_match = re.search(r'\{tmdb[=\-](\d+)\}', title)
|
||||
if tmdb_match:
|
||||
metainfo['tmdbid'] = tmdb_match.group(1)
|
||||
title = re.sub(r'\{tmdb[=\-](\d+)\}', '', title).strip()
|
||||
|
||||
# 计算季集总数
|
||||
if metainfo.get('begin_season') and metainfo.get('end_season'):
|
||||
if metainfo['begin_season'] > metainfo['end_season']:
|
||||
@@ -169,3 +197,67 @@ def find_metainfo(title: str) -> Tuple[str, dict]:
|
||||
elif metainfo.get('begin_episode') and not metainfo.get('end_episode'):
|
||||
metainfo['total_episode'] = 1
|
||||
return title, metainfo
|
||||
|
||||
|
||||
def test_find_metainfo():
|
||||
"""
|
||||
测试find_metainfo函数的各种ID识别格式
|
||||
"""
|
||||
test_cases = [
|
||||
# 测试 [tmdbid=xxxx] 格式
|
||||
("The Vampire Diaries (2009) [tmdbid=18165]", "18165"),
|
||||
# 测试 [tmdbid-xxxx] 格式
|
||||
("Inception (2010) [tmdbid-27205]", "27205"),
|
||||
# 测试 [tmdb=xxxx] 格式
|
||||
("Breaking Bad (2008) [tmdb=1396]", "1396"),
|
||||
# 测试 [tmdb-xxxx] 格式
|
||||
("Interstellar (2014) [tmdb-157336]", "157336"),
|
||||
# 测试 {tmdbid=xxxx} 格式
|
||||
("Stranger Things (2016) {tmdbid=66732}", "66732"),
|
||||
# 测试 {tmdbid-xxxx} 格式
|
||||
("The Matrix (1999) {tmdbid-603}", "603"),
|
||||
# 测试 {tmdb=xxxx} 格式
|
||||
("Game of Thrones (2011) {tmdb=1399}", "1399"),
|
||||
# 测试 {tmdb-xxxx} 格式
|
||||
("Avatar (2009) {tmdb-19995}", "19995"),
|
||||
]
|
||||
|
||||
for title, expected_tmdbid in test_cases:
|
||||
cleaned_title, metainfo = find_metainfo(title)
|
||||
found_tmdbid = metainfo.get('tmdbid')
|
||||
|
||||
print(f"原标题: {title}")
|
||||
print(f"清理后标题: {cleaned_title}")
|
||||
print(f"期望的tmdbid: {expected_tmdbid}")
|
||||
print(f"识别的tmdbid: {found_tmdbid}")
|
||||
print(f"结果: {'通过' if found_tmdbid == expected_tmdbid else '失败'}")
|
||||
print("-" * 50)
|
||||
|
||||
|
||||
def test_meta_info_path():
|
||||
"""
|
||||
测试MetaInfoPath函数
|
||||
"""
|
||||
# 测试文件路径
|
||||
path_tests = [
|
||||
# 文件名中包含tmdbid
|
||||
Path("/movies/The Vampire Diaries (2009) [tmdbid=18165]/The.Vampire.Diaries.S01E01.1080p.mkv"),
|
||||
# 目录名中包含tmdbid
|
||||
Path("/movies/Inception (2010) [tmdbid-27205]/Inception.2010.1080p.mkv"),
|
||||
# 父目录名中包含tmdbid
|
||||
Path("/movies/Breaking Bad (2008) [tmdb=1396]/Season 1/Breaking.Bad.S01E01.1080p.mkv"),
|
||||
# 祖父目录名中包含tmdbid
|
||||
Path("/tv/Game of Thrones (2011) {tmdb=1399}/Season 1/Game.of.Thrones.S01E01.1080p.mkv"),
|
||||
]
|
||||
|
||||
for path in path_tests:
|
||||
meta = MetaInfoPath(path)
|
||||
print(f"测试路径: {path}")
|
||||
print(f"识别结果: tmdbid={meta.tmdbid}")
|
||||
print("-" * 50)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 运行测试函数
|
||||
# test_find_metainfo()
|
||||
test_meta_info_path()
|
||||
|
||||
@@ -92,7 +92,7 @@ class WallpaperHelper(metaclass=Singleton):
|
||||
# 如果返回的是图片格式
|
||||
content_type = resp.headers.get('Content-Type')
|
||||
if content_type and content_type.lower() == 'image/jpeg':
|
||||
wallpaper_list.append(resp.url)
|
||||
wallpaper_list.append(settings.CUSTOMIZE_WALLPAPER_API_URL)
|
||||
else:
|
||||
try:
|
||||
result = resp.json()
|
||||
|
||||
@@ -435,6 +435,17 @@ class AliPan(StorageBase, metaclass=Singleton):
|
||||
break
|
||||
return items
|
||||
|
||||
def _delay_get_item(self, path: Path) -> Optional[schemas.FileItem]:
|
||||
"""
|
||||
自动延迟重试 get_item 模块
|
||||
"""
|
||||
for _ in range(2):
|
||||
time.sleep(2)
|
||||
fileitem = self.get_item(path)
|
||||
if fileitem:
|
||||
return fileitem
|
||||
return None
|
||||
|
||||
def create_folder(self, parent_item: schemas.FileItem, name: str) -> Optional[schemas.FileItem]:
|
||||
"""
|
||||
创建目录
|
||||
@@ -457,7 +468,7 @@ class AliPan(StorageBase, metaclass=Singleton):
|
||||
# 缓存新目录
|
||||
new_path = Path(parent_item.path) / name
|
||||
self._id_cache[str(new_path)] = (resp.get("drive_id"), resp.get("file_id"))
|
||||
return self.get_item(new_path)
|
||||
return self._delay_get_item(new_path)
|
||||
|
||||
@staticmethod
|
||||
def _calculate_pre_hash(file_path: Path):
|
||||
@@ -676,7 +687,7 @@ class AliPan(StorageBase, metaclass=Singleton):
|
||||
chunk_size=chunk_size)
|
||||
if create_res.get('rapid_upload', False):
|
||||
logger.info(f"【阿里云盘】{target_name} 秒传完成!")
|
||||
return self.get_item(target_path)
|
||||
return self._delay_get_item(target_path)
|
||||
|
||||
if create_res.get("exist", False):
|
||||
logger.info(f"【阿里云盘】{target_name} 已存在")
|
||||
@@ -919,7 +930,7 @@ class AliPan(StorageBase, metaclass=Singleton):
|
||||
return False
|
||||
# 重命名
|
||||
new_path = Path(path) / fileitem.name
|
||||
new_file = self.get_item(new_path)
|
||||
new_file = self._delay_get_item(new_path)
|
||||
self.rename(new_file, new_name)
|
||||
# 更新缓存
|
||||
del self._id_cache[fileitem.path]
|
||||
|
||||
@@ -306,6 +306,17 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
||||
sha1.update(chunk)
|
||||
return sha1.hexdigest()
|
||||
|
||||
def _delay_get_item(self, path: Path) -> Optional[schemas.FileItem]:
|
||||
"""
|
||||
自动延迟重试 get_item 模块
|
||||
"""
|
||||
for _ in range(2):
|
||||
time.sleep(2)
|
||||
fileitem = self.get_item(path)
|
||||
if fileitem:
|
||||
return fileitem
|
||||
return None
|
||||
|
||||
def init_storage(self):
|
||||
pass
|
||||
|
||||
@@ -513,7 +524,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
||||
# Step 3: 秒传
|
||||
if init_result.get("status") == 2:
|
||||
logger.info(f"【115】{target_name} 秒传成功")
|
||||
return self.get_item(target_path)
|
||||
return self._delay_get_item(target_path)
|
||||
|
||||
# Step 4: 获取上传凭证
|
||||
token_resp = self._request_api(
|
||||
@@ -618,7 +629,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
||||
logger.error(f"【115】{target_name} 上传失败: {e.status}, 错误码: {e.code}, 详情: {e.message}")
|
||||
return None
|
||||
# 返回结果
|
||||
return self.get_item(target_path)
|
||||
return self._delay_get_item(target_path)
|
||||
|
||||
def download(self, fileitem: schemas.FileItem, path: Path = None) -> Optional[Path]:
|
||||
"""
|
||||
@@ -783,7 +794,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
||||
return False
|
||||
if resp["state"]:
|
||||
new_path = Path(path) / fileitem.name
|
||||
new_item = self.get_item(new_path)
|
||||
new_item = self._delay_get_item(new_path)
|
||||
self.rename(new_item, new_name)
|
||||
# 更新缓存
|
||||
del self._id_cache[fileitem.path]
|
||||
@@ -811,7 +822,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
||||
return False
|
||||
if resp["state"]:
|
||||
new_path = Path(path) / fileitem.name
|
||||
new_file = self.get_item(new_path)
|
||||
new_file = self._delay_get_item(new_path)
|
||||
self.rename(new_file, new_name)
|
||||
# 更新缓存
|
||||
del self._id_cache[fileitem.path]
|
||||
|
||||
@@ -21,8 +21,8 @@ class FilterModule(_ModuleBase):
|
||||
rule_set: Dict[str, dict] = {
|
||||
# 蓝光原盘
|
||||
"BLU": {
|
||||
"include": [r'Blu-?Ray.+VC-?1|Blu-?Ray.+AVC|UHD.+blu-?ray.+HEVC|MiniBD'],
|
||||
"exclude": [r'[Hx].?264|[Hx].?265|WEB-?DL|WEB-?RIP|REMUX']
|
||||
"include": [r'(?i)(\bBlu-?Ray\b.*\b(?:VC-?1|AVC|MPEG-?2)\b|\b(?:UHD|4K|2160p)\b(?:.*Blu-?Ray)?.*\b(?:HEVC|H\.?265)\b|\bBlu-?Ray\b.*\b(?:UHD|4K|2160p)\b.*\b(?:HEVC|H\.?265)\b|\b(?:COMPLETE|FULL)\b.*\b(?:(?:UHD|4K|2160p)\b.*)?Blu-?Ray\b|\b(BD25|BD50|BD66|BD100|BDMV|MiniBD)\b)'],
|
||||
"exclude": [r'(?i)(\b[XH]\.?264\b|\b[XH]\.?265\b|\bWEB-?DL\b|\bWEB-?RIP\b|\bHDTV(?:RIP)?\b|\bREMUX\b|\bBDRip\b|\bBRRip\b|\bHDRip\b|\bENCODE\b|\b(?<!WEB-|HDTV)RIP\b)']
|
||||
},
|
||||
# 4K
|
||||
"4K": {
|
||||
|
||||
@@ -1334,7 +1334,18 @@ class TmdbApi:
|
||||
return []
|
||||
try:
|
||||
logger.debug(f"正在获取剧集组:{group_id}...")
|
||||
return self.tv.group_episodes(group_id) or []
|
||||
group_seasons = self.tv.group_episodes(group_id) or []
|
||||
return [
|
||||
{
|
||||
**group_season,
|
||||
"episodes": [
|
||||
{**ep, "episode_number": idx}
|
||||
# 剧集组中每个季的episode_number从1开始
|
||||
for idx, ep in enumerate(group_season.get("episodes", []), start=1)
|
||||
]
|
||||
}
|
||||
for group_season in group_seasons
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return []
|
||||
@@ -1348,9 +1359,6 @@ class TmdbApi:
|
||||
return {}
|
||||
for group_season in group_seasons:
|
||||
if group_season.get('order') == season:
|
||||
# 剧集组中每个季的episode_number从1开始
|
||||
for i, e in enumerate(group_season.get('episodes', []), start=1):
|
||||
e['episode_number'] = i
|
||||
return group_season
|
||||
return {}
|
||||
|
||||
|
||||
@@ -908,3 +908,20 @@ class StringUtils:
|
||||
:return: 如果elem有效(非None且长度大于0),返回True;否则返回False
|
||||
"""
|
||||
return elem is not None and len(elem) > 0
|
||||
|
||||
@staticmethod
|
||||
def is_link(text: str) -> bool:
|
||||
"""
|
||||
检查文件是否为链接地址,支持各类协议
|
||||
:param text: 要检查的文本
|
||||
:return: 如果URL有效,返回True;否则返回False
|
||||
"""
|
||||
if not text:
|
||||
return False
|
||||
# 检查是否以http、https、ftp等协议开头
|
||||
if re.match(r'^(http|https|ftp|ftps|sftp|ws|wss)://', text):
|
||||
return True
|
||||
# 检查是否为IP地址或域名
|
||||
if re.match(r'^[a-zA-Z0-9.-]+(\.[a-zA-Z]{2,})?$', text):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -12,7 +12,7 @@ class Tokens:
|
||||
self.load_text(text)
|
||||
|
||||
def load_text(self, text):
|
||||
splitted_text = re.split(r"\.|\s+|\(|\)|\[|]|-|\+|【|】|/|~|;|&|\||#|_|「|」|~", text)
|
||||
splitted_text = re.split(r"\.|\s+|\(|\)|\[|]|-|【|】|/|~|;|&|\||#|_|「|」|~", text)
|
||||
for sub_text in splitted_text:
|
||||
if sub_text:
|
||||
self._tokens.append(sub_text)
|
||||
|
||||
@@ -37,7 +37,7 @@ def upgrade() -> None:
|
||||
'text': '{% if site_name %}站点:{{ site_name }}{% endif %}'
|
||||
'{% if resource_term %}\\n质量:{{ resource_term }}{% endif %}'
|
||||
'{% if size %}\\n大小:{{ size }}{% endif %}'
|
||||
'{% if title %}\\n种子:{{ title }}{% endif %}'
|
||||
'{% if torrent_title %}\\n种子:{{ torrent_title }}{% endif %}'
|
||||
'{% if pubdate %}\\n发布时间:{{ pubdate }}{% endif %}'
|
||||
'{% if freedate %}\\n免费时间:{{ freedate }}{% endif %}'
|
||||
'{% if seeders %}\\n做种数:{{ seeders }}{% endif %}'
|
||||
|
||||
@@ -668,7 +668,7 @@ meta_cases = [{
|
||||
"restype": "UHD BluRay DoVi",
|
||||
"pix": "1080p",
|
||||
"video_codec": "X265",
|
||||
"audio_codec": "DD 7.1"
|
||||
"audio_codec": "DD+ 7.1"
|
||||
}
|
||||
}, {
|
||||
"title": "Childhood.In.A.Capsule.S01E16.2022.1080p.KKTV.WEB-DL.X264.AAC-ADWeb.mkv",
|
||||
@@ -968,7 +968,7 @@ meta_cases = [{
|
||||
"year": "2023",
|
||||
"part": "",
|
||||
"season": "S02",
|
||||
"episode": "E01-E08",
|
||||
"episode": "",
|
||||
"restype": "WEB-DL",
|
||||
"pix": "2160p",
|
||||
"video_codec": "H265",
|
||||
@@ -1016,7 +1016,7 @@ meta_cases = [{
|
||||
"year": "2019",
|
||||
"part": "",
|
||||
"season": "S01",
|
||||
"episode": "E01-E36",
|
||||
"episode": "",
|
||||
"restype": "WEB-DL",
|
||||
"pix": "2160p",
|
||||
"video_codec": "H265",
|
||||
@@ -1037,4 +1037,84 @@ meta_cases = [{
|
||||
"video_codec": "",
|
||||
"audio_codec": ""
|
||||
}
|
||||
}, {
|
||||
"path": "/movies/The Vampire Diaries (2009) [tmdbid=18165]/The.Vampire.Diaries.S01E01.1080p.mkv",
|
||||
"target": {
|
||||
"type": "电视剧",
|
||||
"cn_name": "",
|
||||
"en_name": "The Vampire Diaries",
|
||||
"year": "2009",
|
||||
"part": "",
|
||||
"season": "S01",
|
||||
"episode": "E01",
|
||||
"restype": "",
|
||||
"pix": "1080p",
|
||||
"video_codec": "",
|
||||
"audio_codec": "",
|
||||
"tmdbid": 18165
|
||||
}
|
||||
}, {
|
||||
"path": "/movies/Inception (2010) [tmdbid-27205]/Inception.2010.1080p.mkv",
|
||||
"target": {
|
||||
"type": "未知",
|
||||
"cn_name": "",
|
||||
"en_name": "Inception",
|
||||
"year": "2010",
|
||||
"part": "",
|
||||
"season": "",
|
||||
"episode": "",
|
||||
"restype": "",
|
||||
"pix": "1080p",
|
||||
"video_codec": "",
|
||||
"audio_codec": "",
|
||||
"tmdbid": 27205
|
||||
}
|
||||
}, {
|
||||
"path": "/movies/Breaking Bad (2008) [tmdb=1396]/Season 1/Breaking.Bad.S01E01.1080p.mkv",
|
||||
"target": {
|
||||
"type": "电视剧",
|
||||
"cn_name": "",
|
||||
"en_name": "Breaking Bad",
|
||||
"year": "2008",
|
||||
"part": "",
|
||||
"season": "S01",
|
||||
"episode": "E01",
|
||||
"restype": "",
|
||||
"pix": "1080p",
|
||||
"video_codec": "",
|
||||
"audio_codec": "",
|
||||
"tmdbid": 1396
|
||||
}
|
||||
}, {
|
||||
"path": "/tv/Game of Thrones (2011) {tmdb=1399}/Season 1/Game.of.Thrones.S01E01.1080p.mkv",
|
||||
"target": {
|
||||
"type": "电视剧",
|
||||
"cn_name": "",
|
||||
"en_name": "Game Of Thrones",
|
||||
"year": "2011",
|
||||
"part": "",
|
||||
"season": "S01",
|
||||
"episode": "E01",
|
||||
"restype": "",
|
||||
"pix": "1080p",
|
||||
"video_codec": "",
|
||||
"audio_codec": "",
|
||||
"tmdbid": 1399
|
||||
}
|
||||
}, {
|
||||
"path": "/movies/Avatar (2009) {tmdb-19995}/Avatar.2009.1080p.mkv",
|
||||
"target": {
|
||||
"type": "未知",
|
||||
"cn_name": "",
|
||||
"en_name": "Avatar",
|
||||
"year": "2009",
|
||||
"part": "",
|
||||
"season": "",
|
||||
"episode": "",
|
||||
"restype": "",
|
||||
"pix": "1080p",
|
||||
"video_codec": "",
|
||||
"audio_codec": "",
|
||||
"tmdbid": 19995
|
||||
}
|
||||
}]
|
||||
|
||||
@@ -7,6 +7,7 @@ if __name__ == '__main__':
|
||||
|
||||
# 测试名称识别
|
||||
suite.addTest(MetaInfoTest('test_metainfo'))
|
||||
suite.addTest(MetaInfoTest('test_emby_format_ids'))
|
||||
|
||||
# 运行测试
|
||||
runner = unittest.TextTestRunner()
|
||||
|
||||
@@ -32,4 +32,32 @@ class MetaInfoTest(TestCase):
|
||||
"video_codec": meta_info.video_encode or "",
|
||||
"audio_codec": meta_info.audio_encode or ""
|
||||
}
|
||||
|
||||
# 检查tmdbid
|
||||
if info.get("target").get("tmdbid"):
|
||||
target["tmdbid"] = meta_info.tmdbid
|
||||
|
||||
self.assertEqual(target, info.get("target"))
|
||||
|
||||
def test_emby_format_ids(self):
|
||||
"""
|
||||
测试Emby格式ID识别
|
||||
"""
|
||||
# 测试文件路径
|
||||
test_paths = [
|
||||
# 文件名中包含tmdbid
|
||||
("/movies/The Vampire Diaries (2009) [tmdbid=18165]/The.Vampire.Diaries.S01E01.1080p.mkv", 18165),
|
||||
# 目录名中包含tmdbid
|
||||
("/movies/Inception (2010) [tmdbid-27205]/Inception.2010.1080p.mkv", 27205),
|
||||
# 父目录名中包含tmdbid
|
||||
("/movies/Breaking Bad (2008) [tmdb=1396]/Season 1/Breaking.Bad.S01E01.1080p.mkv", 1396),
|
||||
# 祖父目录名中包含tmdbid
|
||||
("/tv/Game of Thrones (2011) {tmdb=1399}/Season 1/Game.of.Thrones.S01E01.1080p.mkv", 1399),
|
||||
# 测试{tmdb-xxx}格式
|
||||
("/movies/Avatar (2009) {tmdb-19995}/Avatar.2009.1080p.mkv", 19995),
|
||||
]
|
||||
|
||||
for path_str, expected_tmdbid in test_paths:
|
||||
meta = MetaInfoPath(Path(path_str))
|
||||
self.assertEqual(meta.tmdbid, expected_tmdbid,
|
||||
f"路径 {path_str} 期望的tmdbid为 {expected_tmdbid},实际识别为 {meta.tmdbid}")
|
||||
|
||||
@@ -2,6 +2,7 @@ from unittest import TestCase
|
||||
from tests.cases.groups import release_group_cases
|
||||
from app.core.meta.releasegroup import ReleaseGroupsMatcher
|
||||
|
||||
|
||||
class MetaInfoTest(TestCase):
|
||||
def test_release_group(self):
|
||||
for info in release_group_cases:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
APP_VERSION = 'v2.4.8'
|
||||
FRONTEND_VERSION = 'v2.4.8'
|
||||
APP_VERSION = 'v2.4.9'
|
||||
FRONTEND_VERSION = 'v2.4.9'
|
||||
|
||||
Reference in New Issue
Block a user