mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
feat:支持指定剧集组识别和刮削
This commit is contained in:
@@ -83,6 +83,7 @@ def create_subscribe(
|
|||||||
doubanid=subscribe_in.doubanid,
|
doubanid=subscribe_in.doubanid,
|
||||||
bangumiid=subscribe_in.bangumiid,
|
bangumiid=subscribe_in.bangumiid,
|
||||||
mediaid=subscribe_in.mediaid,
|
mediaid=subscribe_in.mediaid,
|
||||||
|
episode_group=subscribe_in.episode_group,
|
||||||
username=current_user.name,
|
username=current_user.name,
|
||||||
best_version=subscribe_in.best_version,
|
best_version=subscribe_in.best_version,
|
||||||
save_path=subscribe_in.save_path,
|
save_path=subscribe_in.save_path,
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ def tmdb_person_credits(person_id: int,
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/{tmdbid}/{season}", summary="TMDB季所有集", response_model=List[schemas.TmdbEpisode])
|
@router.get("/{tmdbid}/{season}", summary="TMDB季所有集", response_model=List[schemas.TmdbEpisode])
|
||||||
def tmdb_season_episodes(tmdbid: int, season: int,
|
def tmdb_season_episodes(tmdbid: int, season: int, episode_group: Optional[str] = None,
|
||||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||||
"""
|
"""
|
||||||
根据TMDBID查询某季的所有信信息
|
根据TMDBID查询某季的所有信信息
|
||||||
"""
|
"""
|
||||||
return TmdbChain().tmdb_episodes(tmdbid=tmdbid, season=season)
|
return TmdbChain().tmdb_episodes(tmdbid=tmdbid, season=season, episode_group=episode_group)
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ def manual_transfer(transer_item: ManualTransferItem,
|
|||||||
doubanid=transer_item.doubanid,
|
doubanid=transer_item.doubanid,
|
||||||
mtype=mtype,
|
mtype=mtype,
|
||||||
season=transer_item.season,
|
season=transer_item.season,
|
||||||
|
episode_group=transer_item.episode_group,
|
||||||
transfer_type=transer_item.transfer_type,
|
transfer_type=transer_item.transfer_type,
|
||||||
epformat=epformat,
|
epformat=epformat,
|
||||||
min_filesize=transer_item.min_filesize,
|
min_filesize=transer_item.min_filesize,
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ class ChainBase(metaclass=ABCMeta):
|
|||||||
tmdbid: Optional[int] = None,
|
tmdbid: Optional[int] = None,
|
||||||
doubanid: Optional[str] = None,
|
doubanid: Optional[str] = None,
|
||||||
bangumiid: Optional[int] = None,
|
bangumiid: Optional[int] = None,
|
||||||
|
episode_group: Optional[str] = None,
|
||||||
cache: bool = True) -> Optional[MediaInfo]:
|
cache: bool = True) -> Optional[MediaInfo]:
|
||||||
"""
|
"""
|
||||||
识别媒体信息,不含Fanart图片
|
识别媒体信息,不含Fanart图片
|
||||||
@@ -158,6 +159,7 @@ class ChainBase(metaclass=ABCMeta):
|
|||||||
:param tmdbid: tmdbid
|
:param tmdbid: tmdbid
|
||||||
:param doubanid: 豆瓣ID
|
:param doubanid: 豆瓣ID
|
||||||
:param bangumiid: BangumiID
|
:param bangumiid: BangumiID
|
||||||
|
:param episode_group: 剧集组
|
||||||
:param cache: 是否使用缓存
|
:param cache: 是否使用缓存
|
||||||
:return: 识别的媒体信息,包括剧集信息
|
:return: 识别的媒体信息,包括剧集信息
|
||||||
"""
|
"""
|
||||||
@@ -173,7 +175,8 @@ class ChainBase(metaclass=ABCMeta):
|
|||||||
doubanid = None
|
doubanid = None
|
||||||
bangumiid = None
|
bangumiid = None
|
||||||
return self.run_module("recognize_media", meta=meta, mtype=mtype,
|
return self.run_module("recognize_media", meta=meta, mtype=mtype,
|
||||||
tmdbid=tmdbid, doubanid=doubanid, bangumiid=bangumiid, cache=cache)
|
tmdbid=tmdbid, doubanid=doubanid, bangumiid=bangumiid,
|
||||||
|
episode_group=episode_group, cache=cache)
|
||||||
|
|
||||||
def match_doubaninfo(self, name: str, imdbid: Optional[str] = None,
|
def match_doubaninfo(self, name: str, imdbid: Optional[str] = None,
|
||||||
mtype: Optional[MediaType] = None, year: Optional[str] = None, season: Optional[int] = None,
|
mtype: Optional[MediaType] = None, year: Optional[str] = None, season: Optional[int] = None,
|
||||||
|
|||||||
+14
-19
@@ -209,7 +209,6 @@ class DownloadChain(ChainBase):
|
|||||||
save_path: Optional[str] = None,
|
save_path: Optional[str] = None,
|
||||||
userid: Union[str, int] = None,
|
userid: Union[str, int] = None,
|
||||||
username: Optional[str] = None,
|
username: Optional[str] = None,
|
||||||
media_category: Optional[str] = None,
|
|
||||||
label: Optional[str] = None) -> Optional[str]:
|
label: Optional[str] = None) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
下载及发送通知
|
下载及发送通知
|
||||||
@@ -222,9 +221,13 @@ class DownloadChain(ChainBase):
|
|||||||
:param save_path: 保存路径
|
:param save_path: 保存路径
|
||||||
:param userid: 用户ID
|
:param userid: 用户ID
|
||||||
:param username: 调用下载的用户名/插件名
|
:param username: 调用下载的用户名/插件名
|
||||||
:param media_category: 自定义媒体类别
|
|
||||||
:param label: 自定义标签
|
:param label: 自定义标签
|
||||||
"""
|
"""
|
||||||
|
_torrent = context.torrent_info
|
||||||
|
_media = context.media_info
|
||||||
|
_meta = context.meta_info
|
||||||
|
_site_downloader = _torrent.site_downloader
|
||||||
|
|
||||||
# 发送资源下载事件,允许外部拦截下载
|
# 发送资源下载事件,允许外部拦截下载
|
||||||
event_data = ResourceDownloadEventData(
|
event_data = ResourceDownloadEventData(
|
||||||
context=context,
|
context=context,
|
||||||
@@ -236,7 +239,7 @@ class DownloadChain(ChainBase):
|
|||||||
"save_path": save_path,
|
"save_path": save_path,
|
||||||
"userid": userid,
|
"userid": userid,
|
||||||
"username": username,
|
"username": username,
|
||||||
"media_category": media_category
|
"media_category": _media.category
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# 触发资源下载事件
|
# 触发资源下载事件
|
||||||
@@ -250,15 +253,11 @@ class DownloadChain(ChainBase):
|
|||||||
f"Reason: {event_data.reason}")
|
f"Reason: {event_data.reason}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
_torrent = context.torrent_info
|
|
||||||
_media = context.media_info
|
|
||||||
_meta = context.meta_info
|
|
||||||
_site_downloader = _torrent.site_downloader
|
|
||||||
|
|
||||||
# 补充完整的media数据
|
# 补充完整的media数据
|
||||||
if not _media.genre_ids:
|
if not _media.genre_ids:
|
||||||
new_media = self.recognize_media(mtype=_media.type, tmdbid=_media.tmdb_id,
|
new_media = self.recognize_media(mtype=_media.type, tmdbid=_media.tmdb_id,
|
||||||
doubanid=_media.douban_id, bangumiid=_media.bangumi_id)
|
doubanid=_media.douban_id, bangumiid=_media.bangumi_id,
|
||||||
|
episode_group=_media.episode_group)
|
||||||
if new_media:
|
if new_media:
|
||||||
_media = new_media
|
_media = new_media
|
||||||
|
|
||||||
@@ -355,7 +354,8 @@ class DownloadChain(ChainBase):
|
|||||||
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,
|
media_category=_media.category,
|
||||||
|
episode_group=_media.episode_group,
|
||||||
note={"source": source}
|
note={"source": source}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -423,7 +423,6 @@ class DownloadChain(ChainBase):
|
|||||||
source: Optional[str] = None,
|
source: Optional[str] = None,
|
||||||
userid: Optional[str] = None,
|
userid: Optional[str] = None,
|
||||||
username: Optional[str] = None,
|
username: Optional[str] = None,
|
||||||
media_category: Optional[str] = None,
|
|
||||||
downloader: Optional[str] = None
|
downloader: Optional[str] = None
|
||||||
) -> Tuple[List[Context], Dict[Union[int, str], Dict[int, NotExistMediaInfo]]]:
|
) -> Tuple[List[Context], Dict[Union[int, str], Dict[int, NotExistMediaInfo]]]:
|
||||||
"""
|
"""
|
||||||
@@ -435,7 +434,6 @@ class DownloadChain(ChainBase):
|
|||||||
:param source: 来源(消息通知、订阅、手工下载等)
|
:param source: 来源(消息通知、订阅、手工下载等)
|
||||||
:param userid: 用户ID
|
:param userid: 用户ID
|
||||||
:param username: 调用下载的用户名/插件名
|
:param username: 调用下载的用户名/插件名
|
||||||
:param media_category: 自定义媒体类别
|
|
||||||
:param downloader: 下载器
|
:param downloader: 下载器
|
||||||
:return: 已经下载的资源列表、剩余未下载到的剧集 no_exists[tmdb_id/douban_id] = {season: NotExistMediaInfo}
|
:return: 已经下载的资源列表、剩余未下载到的剧集 no_exists[tmdb_id/douban_id] = {season: NotExistMediaInfo}
|
||||||
"""
|
"""
|
||||||
@@ -524,7 +522,7 @@ class DownloadChain(ChainBase):
|
|||||||
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, downloader=downloader):
|
downloader=downloader):
|
||||||
# 下载成功
|
# 下载成功
|
||||||
logger.info(f"{context.torrent_info.title} 添加下载成功")
|
logger.info(f"{context.torrent_info.title} 添加下载成功")
|
||||||
downloaded_list.append(context)
|
downloaded_list.append(context)
|
||||||
@@ -609,8 +607,7 @@ class DownloadChain(ChainBase):
|
|||||||
source=source,
|
source=source,
|
||||||
userid=userid,
|
userid=userid,
|
||||||
username=username,
|
username=username,
|
||||||
media_category=media_category,
|
downloader=downloader
|
||||||
downloader=downloader,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 下载
|
# 下载
|
||||||
@@ -618,7 +615,6 @@ class DownloadChain(ChainBase):
|
|||||||
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,
|
|
||||||
downloader=downloader)
|
downloader=downloader)
|
||||||
|
|
||||||
if download_id:
|
if download_id:
|
||||||
@@ -690,7 +686,6 @@ class DownloadChain(ChainBase):
|
|||||||
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,
|
|
||||||
downloader=downloader)
|
downloader=downloader)
|
||||||
if download_id:
|
if download_id:
|
||||||
# 下载成功
|
# 下载成功
|
||||||
@@ -780,7 +775,6 @@ class DownloadChain(ChainBase):
|
|||||||
source=source,
|
source=source,
|
||||||
userid=userid,
|
userid=userid,
|
||||||
username=username,
|
username=username,
|
||||||
media_category=media_category,
|
|
||||||
downloader=downloader
|
downloader=downloader
|
||||||
)
|
)
|
||||||
if not download_id:
|
if not download_id:
|
||||||
@@ -866,7 +860,8 @@ class DownloadChain(ChainBase):
|
|||||||
# 补充媒体信息
|
# 补充媒体信息
|
||||||
mediainfo: MediaInfo = self.recognize_media(mtype=mediainfo.type,
|
mediainfo: MediaInfo = self.recognize_media(mtype=mediainfo.type,
|
||||||
tmdbid=mediainfo.tmdb_id,
|
tmdbid=mediainfo.tmdb_id,
|
||||||
doubanid=mediainfo.douban_id)
|
doubanid=mediainfo.douban_id,
|
||||||
|
episode_group=mediainfo.episode_group)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
logger.error(f"媒体信息识别失败!")
|
logger.error(f"媒体信息识别失败!")
|
||||||
return False, {}
|
return False, {}
|
||||||
|
|||||||
+8
-6
@@ -42,13 +42,13 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
return self.run_module("metadata_nfo", meta=meta, mediainfo=mediainfo, season=season, episode=episode)
|
return self.run_module("metadata_nfo", meta=meta, mediainfo=mediainfo, season=season, episode=episode)
|
||||||
|
|
||||||
def recognize_by_meta(self, metainfo: MetaBase) -> Optional[MediaInfo]:
|
def recognize_by_meta(self, metainfo: MetaBase, episode_group: Optional[str] = None) -> Optional[MediaInfo]:
|
||||||
"""
|
"""
|
||||||
根据主副标题识别媒体信息
|
根据主副标题识别媒体信息
|
||||||
"""
|
"""
|
||||||
title = metainfo.title
|
title = metainfo.title
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
mediainfo: MediaInfo = self.recognize_media(meta=metainfo)
|
mediainfo: MediaInfo = self.recognize_media(meta=metainfo, episode_group=episode_group)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
# 尝试使用辅助识别,如果有注册响应事件的话
|
# 尝试使用辅助识别,如果有注册响应事件的话
|
||||||
if eventmanager.check(ChainEventType.NameRecognize):
|
if eventmanager.check(ChainEventType.NameRecognize):
|
||||||
@@ -112,7 +112,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
|||||||
# 重新识别
|
# 重新识别
|
||||||
return self.recognize_media(meta=org_meta)
|
return self.recognize_media(meta=org_meta)
|
||||||
|
|
||||||
def recognize_by_path(self, path: str) -> Optional[Context]:
|
def recognize_by_path(self, path: str, episode_group: Optional[str] = None) -> Optional[Context]:
|
||||||
"""
|
"""
|
||||||
根据文件路径识别媒体信息
|
根据文件路径识别媒体信息
|
||||||
"""
|
"""
|
||||||
@@ -121,7 +121,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
|||||||
# 元数据
|
# 元数据
|
||||||
file_meta = MetaInfoPath(file_path)
|
file_meta = MetaInfoPath(file_path)
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
mediainfo = self.recognize_media(meta=file_meta)
|
mediainfo = self.recognize_media(meta=file_meta, episode_group=episode_group)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
# 尝试使用辅助识别,如果有注册响应事件的话
|
# 尝试使用辅助识别,如果有注册响应事件的话
|
||||||
if eventmanager.check(ChainEventType.NameRecognize):
|
if eventmanager.check(ChainEventType.NameRecognize):
|
||||||
@@ -474,7 +474,8 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
|||||||
if not file_meta.begin_episode:
|
if not file_meta.begin_episode:
|
||||||
logger.warn(f"{filepath.name} 无法识别文件集数!")
|
logger.warn(f"{filepath.name} 无法识别文件集数!")
|
||||||
return
|
return
|
||||||
file_mediainfo = self.recognize_media(meta=file_meta, tmdbid=mediainfo.tmdb_id)
|
file_mediainfo = self.recognize_media(meta=file_meta, tmdbid=mediainfo.tmdb_id,
|
||||||
|
episode_group=mediainfo.episode_group)
|
||||||
if not file_mediainfo:
|
if not file_mediainfo:
|
||||||
logger.warn(f"{filepath.name} 无法识别文件媒体信息!")
|
logger.warn(f"{filepath.name} 无法识别文件媒体信息!")
|
||||||
return
|
return
|
||||||
@@ -483,7 +484,8 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
|||||||
if overwrite or not self.storagechain.get_file_item(storage=fileitem.storage, path=nfo_path):
|
if overwrite or not self.storagechain.get_file_item(storage=fileitem.storage, path=nfo_path):
|
||||||
# 获取集的nfo文件
|
# 获取集的nfo文件
|
||||||
episode_nfo = self.metadata_nfo(meta=file_meta, mediainfo=file_mediainfo,
|
episode_nfo = self.metadata_nfo(meta=file_meta, mediainfo=file_mediainfo,
|
||||||
season=file_meta.begin_season, episode=file_meta.begin_episode)
|
season=file_meta.begin_season,
|
||||||
|
episode=file_meta.begin_episode)
|
||||||
if episode_nfo:
|
if episode_nfo:
|
||||||
# 保存或上传nfo文件到上级目录
|
# 保存或上传nfo文件到上级目录
|
||||||
if not parent:
|
if not parent:
|
||||||
|
|||||||
+23
-10
@@ -60,6 +60,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
doubanid: Optional[str] = None,
|
doubanid: Optional[str] = None,
|
||||||
bangumiid: Optional[int] = None,
|
bangumiid: Optional[int] = None,
|
||||||
mediaid: Optional[str] = None,
|
mediaid: Optional[str] = None,
|
||||||
|
episode_group: Optional[str] = None,
|
||||||
season: Optional[int] = None,
|
season: Optional[int] = None,
|
||||||
channel: MessageChannel = None,
|
channel: MessageChannel = None,
|
||||||
source: Optional[str] = None,
|
source: Optional[str] = None,
|
||||||
@@ -117,7 +118,8 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
mediainfo = __get_event_meida(mediaid, metainfo)
|
mediainfo = __get_event_meida(mediaid, metainfo)
|
||||||
else:
|
else:
|
||||||
# 使用TMDBID识别
|
# 使用TMDBID识别
|
||||||
mediainfo = self.recognize_media(meta=metainfo, mtype=mtype, tmdbid=tmdbid, cache=False)
|
mediainfo = self.recognize_media(meta=metainfo, mtype=mtype, tmdbid=tmdbid,
|
||||||
|
episode_group=episode_group, cache=False)
|
||||||
else:
|
else:
|
||||||
if doubanid:
|
if doubanid:
|
||||||
# 豆瓣识别模式,不使用缓存
|
# 豆瓣识别模式,不使用缓存
|
||||||
@@ -134,7 +136,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
|
|
||||||
# 使用名称识别兜底
|
# 使用名称识别兜底
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
mediainfo = self.recognize_media(meta=metainfo)
|
mediainfo = self.recognize_media(meta=metainfo, episode_group=episode_group)
|
||||||
|
|
||||||
# 识别失败
|
# 识别失败
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
@@ -153,6 +155,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
tmdbid=mediainfo.tmdb_id,
|
tmdbid=mediainfo.tmdb_id,
|
||||||
doubanid=mediainfo.douban_id,
|
doubanid=mediainfo.douban_id,
|
||||||
bangumiid=mediainfo.bangumi_id,
|
bangumiid=mediainfo.bangumi_id,
|
||||||
|
episode_group=episode_group,
|
||||||
cache=False)
|
cache=False)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
logger.error(f"媒体信息识别失败!")
|
logger.error(f"媒体信息识别失败!")
|
||||||
@@ -207,7 +210,7 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
'save_path': self.__get_default_subscribe_config(mediainfo.type, "save_path") if not kwargs.get(
|
'save_path': self.__get_default_subscribe_config(mediainfo.type, "save_path") if not kwargs.get(
|
||||||
"save_path") else kwargs.get("save_path"),
|
"save_path") else kwargs.get("save_path"),
|
||||||
'filter_groups': self.__get_default_subscribe_config(mediainfo.type, "filter_groups") if not kwargs.get(
|
'filter_groups': self.__get_default_subscribe_config(mediainfo.type, "filter_groups") if not kwargs.get(
|
||||||
"filter_groups") else kwargs.get("filter_groups"),
|
"filter_groups") else kwargs.get("filter_groups")
|
||||||
})
|
})
|
||||||
sid, err_msg = self.subscribeoper.add(mediainfo=mediainfo, season=season, username=username, **kwargs)
|
sid, err_msg = self.subscribeoper.add(mediainfo=mediainfo, season=season, username=username, **kwargs)
|
||||||
if not sid:
|
if not sid:
|
||||||
@@ -383,6 +386,11 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
logger.info(
|
logger.info(
|
||||||
f'{subscribe.name} 正在洗版,{torrent_info.title} 优先级低于或等于已下载优先级')
|
f'{subscribe.name} 正在洗版,{torrent_info.title} 优先级低于或等于已下载优先级')
|
||||||
continue
|
continue
|
||||||
|
# 更新订阅自定义属性
|
||||||
|
if subscribe.media_category:
|
||||||
|
torrent_mediainfo.category = subscribe.media_category
|
||||||
|
if subscribe.episode_group:
|
||||||
|
torrent_mediainfo.episode_group = subscribe.episode_group
|
||||||
matched_contexts.append(context)
|
matched_contexts.append(context)
|
||||||
|
|
||||||
if not matched_contexts:
|
if not matched_contexts:
|
||||||
@@ -398,7 +406,6 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
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,
|
|
||||||
downloader=subscribe.downloader,
|
downloader=subscribe.downloader,
|
||||||
source=self.get_subscribe_source_keyword(subscribe)
|
source=self.get_subscribe_source_keyword(subscribe)
|
||||||
)
|
)
|
||||||
@@ -603,9 +610,10 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
logger.debug(f'开始匹配站点:{domain},共缓存了 {len(contexts)} 个种子...')
|
logger.debug(f'开始匹配站点:{domain},共缓存了 {len(contexts)} 个种子...')
|
||||||
for context in contexts:
|
for context in contexts:
|
||||||
# 提取信息
|
# 提取信息
|
||||||
torrent_meta = copy.deepcopy(context.meta_info)
|
_context = copy.deepcopy(context)
|
||||||
torrent_mediainfo = copy.deepcopy(context.media_info)
|
torrent_meta = _context.meta_info
|
||||||
torrent_info = context.torrent_info
|
torrent_mediainfo = _context.media_info
|
||||||
|
torrent_info = _context.torrent_info
|
||||||
|
|
||||||
# 不在订阅站点范围的不处理
|
# 不在订阅站点范围的不处理
|
||||||
sub_sites = self.get_sub_sites(subscribe)
|
sub_sites = self.get_sub_sites(subscribe)
|
||||||
@@ -736,7 +744,12 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
|
|
||||||
# 匹配成功
|
# 匹配成功
|
||||||
logger.info(f'{mediainfo.title_year} 匹配成功:{torrent_info.title}')
|
logger.info(f'{mediainfo.title_year} 匹配成功:{torrent_info.title}')
|
||||||
_match_context.append(context)
|
# 自定义属性
|
||||||
|
if subscribe.media_category:
|
||||||
|
torrent_mediainfo.category = subscribe.media_category
|
||||||
|
if subscribe.episode_group:
|
||||||
|
torrent_mediainfo.episode_group = subscribe.episode_group
|
||||||
|
_match_context.append(_context)
|
||||||
|
|
||||||
if not _match_context:
|
if not _match_context:
|
||||||
# 未匹配到资源
|
# 未匹配到资源
|
||||||
@@ -752,7 +765,6 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
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,
|
|
||||||
downloader=subscribe.downloader,
|
downloader=subscribe.downloader,
|
||||||
source=self.get_subscribe_source_keyword(subscribe)
|
source=self.get_subscribe_source_keyword(subscribe)
|
||||||
)
|
)
|
||||||
@@ -1274,7 +1286,8 @@ class SubscribeChain(ChainBase, metaclass=Singleton):
|
|||||||
# 查询TMDB中的集信息
|
# 查询TMDB中的集信息
|
||||||
tmdb_episodes = self.tmdbchain.tmdb_episodes(
|
tmdb_episodes = self.tmdbchain.tmdb_episodes(
|
||||||
tmdbid=subscribe.tmdbid,
|
tmdbid=subscribe.tmdbid,
|
||||||
season=subscribe.season
|
season=subscribe.season,
|
||||||
|
episode_group=subscribe.episode_group
|
||||||
)
|
)
|
||||||
if tmdb_episodes:
|
if tmdb_episodes:
|
||||||
for episode in tmdb_episodes:
|
for episode in tmdb_episodes:
|
||||||
|
|||||||
+3
-2
@@ -70,13 +70,14 @@ class TmdbChain(ChainBase, metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
return self.run_module("tmdb_seasons", tmdbid=tmdbid)
|
return self.run_module("tmdb_seasons", tmdbid=tmdbid)
|
||||||
|
|
||||||
def tmdb_episodes(self, tmdbid: int, season: int) -> List[schemas.TmdbEpisode]:
|
def tmdb_episodes(self, tmdbid: int, season: int, episode_group: Optional[str] = None) -> List[schemas.TmdbEpisode]:
|
||||||
"""
|
"""
|
||||||
根据TMDBID查询某季的所有信信息
|
根据TMDBID查询某季的所有信信息
|
||||||
:param tmdbid: TMDBID
|
:param tmdbid: TMDBID
|
||||||
:param season: 季
|
:param season: 季
|
||||||
|
:param episode_group: 剧集组
|
||||||
"""
|
"""
|
||||||
return self.run_module("tmdb_episodes", tmdbid=tmdbid, season=season)
|
return self.run_module("tmdb_episodes", tmdbid=tmdbid, season=season, episode_group=episode_group)
|
||||||
|
|
||||||
def movie_similar(self, tmdbid: int) -> Optional[List[MediaInfo]]:
|
def movie_similar(self, tmdbid: int) -> Optional[List[MediaInfo]]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
+12
-6
@@ -623,7 +623,8 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
# 下载记录中已存在识别信息
|
# 下载记录中已存在识别信息
|
||||||
mediainfo: Optional[MediaInfo] = self.recognize_media(mtype=MediaType(download_history.type),
|
mediainfo: Optional[MediaInfo] = self.recognize_media(mtype=MediaType(download_history.type),
|
||||||
tmdbid=download_history.tmdbid,
|
tmdbid=download_history.tmdbid,
|
||||||
doubanid=download_history.doubanid)
|
doubanid=download_history.doubanid,
|
||||||
|
episode_group=download_history.episode_group)
|
||||||
if mediainfo:
|
if mediainfo:
|
||||||
# 更新自定义媒体类别
|
# 更新自定义媒体类别
|
||||||
if download_history.media_category:
|
if download_history.media_category:
|
||||||
@@ -681,7 +682,8 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
season_num = 1
|
season_num = 1
|
||||||
task.episodes_info = self.tmdbchain.tmdb_episodes(
|
task.episodes_info = self.tmdbchain.tmdb_episodes(
|
||||||
tmdbid=task.mediainfo.tmdb_id,
|
tmdbid=task.mediainfo.tmdb_id,
|
||||||
season=season_num
|
season=season_num,
|
||||||
|
episode_group=task.mediainfo.episode_group
|
||||||
)
|
)
|
||||||
|
|
||||||
# 查询整理目标目录
|
# 查询整理目标目录
|
||||||
@@ -798,7 +800,8 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
# 按TMDBID识别
|
# 按TMDBID识别
|
||||||
mediainfo = self.recognize_media(mtype=mtype,
|
mediainfo = self.recognize_media(mtype=mtype,
|
||||||
tmdbid=downloadhis.tmdbid,
|
tmdbid=downloadhis.tmdbid,
|
||||||
doubanid=downloadhis.doubanid)
|
doubanid=downloadhis.doubanid,
|
||||||
|
episode_group=downloadhis.episode_group)
|
||||||
if mediainfo:
|
if mediainfo:
|
||||||
# 补充图片
|
# 补充图片
|
||||||
self.obtain_images(mediainfo)
|
self.obtain_images(mediainfo)
|
||||||
@@ -1214,12 +1217,12 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
# 查询媒体信息
|
# 查询媒体信息
|
||||||
if mtype and mediaid:
|
if mtype and mediaid:
|
||||||
mediainfo = self.recognize_media(mtype=mtype, tmdbid=int(mediaid) if str(mediaid).isdigit() else None,
|
mediainfo = self.recognize_media(mtype=mtype, tmdbid=int(mediaid) if str(mediaid).isdigit() else None,
|
||||||
doubanid=mediaid)
|
doubanid=mediaid, episode_group=history.episode_group)
|
||||||
if mediainfo:
|
if mediainfo:
|
||||||
# 更新媒体图片
|
# 更新媒体图片
|
||||||
self.obtain_images(mediainfo=mediainfo)
|
self.obtain_images(mediainfo=mediainfo)
|
||||||
else:
|
else:
|
||||||
mediainfo = self.mediachain.recognize_by_path(str(src_path))
|
mediainfo = self.mediachain.recognize_by_path(str(src_path), episode_group=history.episode_group)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
return False, f"未识别到媒体信息,类型:{mtype.value},id:{mediaid}"
|
return False, f"未识别到媒体信息,类型:{mtype.value},id:{mediaid}"
|
||||||
# 重新执行整理
|
# 重新执行整理
|
||||||
@@ -1252,6 +1255,7 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
doubanid: Optional[str] = None,
|
doubanid: Optional[str] = None,
|
||||||
mtype: MediaType = None,
|
mtype: MediaType = None,
|
||||||
season: Optional[int] = None,
|
season: Optional[int] = None,
|
||||||
|
episode_group: Optional[str] = None,
|
||||||
transfer_type: Optional[str] = None,
|
transfer_type: Optional[str] = None,
|
||||||
epformat: EpisodeFormat = None,
|
epformat: EpisodeFormat = None,
|
||||||
min_filesize: Optional[int] = 0,
|
min_filesize: Optional[int] = 0,
|
||||||
@@ -1269,6 +1273,7 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
:param doubanid: 豆瓣ID
|
:param doubanid: 豆瓣ID
|
||||||
:param mtype: 媒体类型
|
:param mtype: 媒体类型
|
||||||
:param season: 季度
|
:param season: 季度
|
||||||
|
:param episode_group: 剧集组
|
||||||
:param transfer_type: 整理类型
|
:param transfer_type: 整理类型
|
||||||
:param epformat: 剧集格式
|
:param epformat: 剧集格式
|
||||||
:param min_filesize: 最小文件大小(MB)
|
:param min_filesize: 最小文件大小(MB)
|
||||||
@@ -1282,7 +1287,8 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
if tmdbid or doubanid:
|
if tmdbid or doubanid:
|
||||||
# 有输入TMDBID时单个识别
|
# 有输入TMDBID时单个识别
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
mediainfo: MediaInfo = self.mediachain.recognize_media(tmdbid=tmdbid, doubanid=doubanid, mtype=mtype)
|
mediainfo: MediaInfo = self.mediachain.recognize_media(tmdbid=tmdbid, doubanid=doubanid,
|
||||||
|
mtype=mtype, episode_group=episode_group)
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
return False, f"媒体信息识别失败,tmdbid:{tmdbid},doubanid:{doubanid},type: {mtype.value}"
|
return False, f"媒体信息识别失败,tmdbid:{tmdbid},doubanid:{doubanid},type: {mtype.value}"
|
||||||
else:
|
else:
|
||||||
|
|||||||
+3
-1
@@ -264,8 +264,10 @@ class MediaInfo:
|
|||||||
next_episode_to_air: dict = field(default_factory=dict)
|
next_episode_to_air: dict = field(default_factory=dict)
|
||||||
# 内容分级
|
# 内容分级
|
||||||
content_rating: str = None
|
content_rating: str = None
|
||||||
# 剧集组
|
# 全部剧集组
|
||||||
episode_groups: List[dict] = field(default_factory=list)
|
episode_groups: List[dict] = field(default_factory=list)
|
||||||
|
# 剧集组
|
||||||
|
episode_group: str = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
# 设置媒体信息
|
# 设置媒体信息
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ class DownloadHistory(Base):
|
|||||||
note = Column(JSON)
|
note = Column(JSON)
|
||||||
# 自定义媒体类别
|
# 自定义媒体类别
|
||||||
media_category = Column(String)
|
media_category = Column(String)
|
||||||
|
# 剧集组
|
||||||
|
episode_group = Column(String)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@db_query
|
@db_query
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ class Subscribe(Base):
|
|||||||
media_category = Column(String)
|
media_category = Column(String)
|
||||||
# 过滤规则组
|
# 过滤规则组
|
||||||
filter_groups = Column(JSON, default=list)
|
filter_groups = Column(JSON, default=list)
|
||||||
|
# 可选剧集组
|
||||||
|
episode_groups = Column(JSON, default=list)
|
||||||
|
# 选择的剧集组
|
||||||
|
episode_group = Column(String)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@db_query
|
@db_query
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class SubscribeHistory(Base):
|
|||||||
media_category = Column(String)
|
media_category = Column(String)
|
||||||
# 过滤规则组
|
# 过滤规则组
|
||||||
filter_groups = Column(JSON, default=list)
|
filter_groups = Column(JSON, default=list)
|
||||||
|
# 剧集组
|
||||||
|
episode_group = Column(String)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@db_query
|
@db_query
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ class TransferHistory(Base):
|
|||||||
date = Column(String, index=True)
|
date = Column(String, index=True)
|
||||||
# 文件清单,以JSON存储
|
# 文件清单,以JSON存储
|
||||||
files = Column(JSON, default=list)
|
files = Column(JSON, default=list)
|
||||||
|
# 剧集组
|
||||||
|
episode_group = Column(String)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@db_query
|
@db_query
|
||||||
|
|||||||
@@ -29,10 +29,12 @@ class SubscribeOper(DbOper):
|
|||||||
tvdbid=mediainfo.tvdb_id,
|
tvdbid=mediainfo.tvdb_id,
|
||||||
doubanid=mediainfo.douban_id,
|
doubanid=mediainfo.douban_id,
|
||||||
bangumiid=mediainfo.bangumi_id,
|
bangumiid=mediainfo.bangumi_id,
|
||||||
|
episode_group=mediainfo.episode_group,
|
||||||
poster=mediainfo.get_poster_image(),
|
poster=mediainfo.get_poster_image(),
|
||||||
backdrop=mediainfo.get_backdrop_image(),
|
backdrop=mediainfo.get_backdrop_image(),
|
||||||
vote=mediainfo.vote_average,
|
vote=mediainfo.vote_average,
|
||||||
description=mediainfo.overview,
|
description=mediainfo.overview,
|
||||||
|
episode_groups=mediainfo.episode_groups,
|
||||||
date=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
date=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||||
**kwargs)
|
**kwargs)
|
||||||
subscribe.create(self._db)
|
subscribe.create(self._db)
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ class TransferHistoryOper(DbOper):
|
|||||||
image=mediainfo.get_poster_image(),
|
image=mediainfo.get_poster_image(),
|
||||||
downloader=downloader,
|
downloader=downloader,
|
||||||
download_hash=download_hash,
|
download_hash=download_hash,
|
||||||
|
episode_group=mediainfo.episode_group,
|
||||||
status=0,
|
status=0,
|
||||||
errmsg=transferinfo.message or '未知错误',
|
errmsg=transferinfo.message or '未知错误',
|
||||||
files=transferinfo.file_list
|
files=transferinfo.file_list
|
||||||
@@ -193,6 +194,7 @@ class TransferHistoryOper(DbOper):
|
|||||||
episodes=meta.episode,
|
episodes=meta.episode,
|
||||||
downloader=downloader,
|
downloader=downloader,
|
||||||
download_hash=download_hash,
|
download_hash=download_hash,
|
||||||
|
episode_group=mediainfo.episode_group,
|
||||||
status=0,
|
status=0,
|
||||||
errmsg="未识别到媒体信息"
|
errmsg="未识别到媒体信息"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ class DoubanModule(_ModuleBase):
|
|||||||
|
|
||||||
def recognize_media(self, meta: MetaBase = None,
|
def recognize_media(self, meta: MetaBase = None,
|
||||||
mtype: MediaType = None,
|
mtype: MediaType = None,
|
||||||
doubanid: str = None,
|
doubanid: Optional[str] = None,
|
||||||
cache: bool = True,
|
cache: Optional[bool] = True,
|
||||||
**kwargs) -> Optional[MediaInfo]:
|
**kwargs) -> Optional[MediaInfo]:
|
||||||
"""
|
"""
|
||||||
识别媒体信息
|
识别媒体信息
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
from typing import Optional, List, Tuple, Union, Dict
|
from typing import Optional, List, Tuple, Union, Dict
|
||||||
|
|
||||||
import cn2an
|
import cn2an
|
||||||
@@ -85,6 +86,7 @@ class TheMovieDbModule(_ModuleBase):
|
|||||||
def recognize_media(self, meta: MetaBase = None,
|
def recognize_media(self, meta: MetaBase = None,
|
||||||
mtype: MediaType = None,
|
mtype: MediaType = None,
|
||||||
tmdbid: Optional[int] = None,
|
tmdbid: Optional[int] = None,
|
||||||
|
episode_group: Optional[str] = None,
|
||||||
cache: Optional[bool] = True,
|
cache: Optional[bool] = True,
|
||||||
**kwargs) -> Optional[MediaInfo]:
|
**kwargs) -> Optional[MediaInfo]:
|
||||||
"""
|
"""
|
||||||
@@ -92,6 +94,7 @@ class TheMovieDbModule(_ModuleBase):
|
|||||||
:param meta: 识别的元数据
|
:param meta: 识别的元数据
|
||||||
:param mtype: 识别的媒体类型,与tmdbid配套
|
:param mtype: 识别的媒体类型,与tmdbid配套
|
||||||
:param tmdbid: tmdbid
|
:param tmdbid: tmdbid
|
||||||
|
:param episode_group: 剧集组
|
||||||
:param cache: 是否使用缓存
|
:param cache: 是否使用缓存
|
||||||
:return: 识别的媒体信息,包括剧集信息
|
:return: 识别的媒体信息,包括剧集信息
|
||||||
"""
|
"""
|
||||||
@@ -116,6 +119,11 @@ class TheMovieDbModule(_ModuleBase):
|
|||||||
meta.tmdbid = tmdbid
|
meta.tmdbid = tmdbid
|
||||||
cache_info = self.cache.get(meta)
|
cache_info = self.cache.get(meta)
|
||||||
|
|
||||||
|
# 查询剧集组
|
||||||
|
group_episodes = []
|
||||||
|
if episode_group:
|
||||||
|
group_episodes = self.tmdb.get_tv_group_episodes(episode_group)
|
||||||
|
|
||||||
# 识别匹配
|
# 识别匹配
|
||||||
if not cache_info or not cache:
|
if not cache_info or not cache:
|
||||||
info = None
|
info = None
|
||||||
@@ -143,7 +151,8 @@ class TheMovieDbModule(_ModuleBase):
|
|||||||
year=meta.year,
|
year=meta.year,
|
||||||
mtype=meta.type,
|
mtype=meta.type,
|
||||||
season_year=meta.year,
|
season_year=meta.year,
|
||||||
season_number=meta.begin_season)
|
season_number=meta.begin_season,
|
||||||
|
group_episodes=group_episodes)
|
||||||
if not info:
|
if not info:
|
||||||
# 去掉年份再查一次
|
# 去掉年份再查一次
|
||||||
info = self.tmdb.match(name=name,
|
info = self.tmdb.match(name=name,
|
||||||
@@ -157,7 +166,8 @@ class TheMovieDbModule(_ModuleBase):
|
|||||||
if not info:
|
if not info:
|
||||||
info = self.tmdb.match(name=name,
|
info = self.tmdb.match(name=name,
|
||||||
year=meta.year,
|
year=meta.year,
|
||||||
mtype=MediaType.TV)
|
mtype=MediaType.TV,
|
||||||
|
group_episodes=group_episodes)
|
||||||
if not info:
|
if not info:
|
||||||
# 去掉年份和类型再查一次
|
# 去掉年份和类型再查一次
|
||||||
info = self.tmdb.match_multi(name=name)
|
info = self.tmdb.match_multi(name=name)
|
||||||
@@ -207,11 +217,61 @@ class TheMovieDbModule(_ModuleBase):
|
|||||||
logger.info(f"{tmdbid} TMDB识别结果:{mediainfo.type.value} "
|
logger.info(f"{tmdbid} TMDB识别结果:{mediainfo.type.value} "
|
||||||
f"{mediainfo.title_year}")
|
f"{mediainfo.title_year}")
|
||||||
|
|
||||||
# 补充剧集年份为季年份
|
# 使用剧集组的集信息和年份
|
||||||
if mediainfo.type == MediaType.TV and mediainfo.episode_groups:
|
if mediainfo.type == MediaType.TV and mediainfo.episode_groups:
|
||||||
episode_years = self.tmdb.get_tv_episode_years(mediainfo.episode_groups)
|
if group_episodes:
|
||||||
if episode_years:
|
# 指定剧集组时
|
||||||
mediainfo.season_years = episode_years
|
seasons = {}
|
||||||
|
season_info = []
|
||||||
|
season_years = {}
|
||||||
|
for group_episode in group_episodes:
|
||||||
|
# 季
|
||||||
|
season = group_episode.get("order")
|
||||||
|
# 集列表
|
||||||
|
episodes = group_episode.get("episodes")
|
||||||
|
if not episodes:
|
||||||
|
continue
|
||||||
|
seasons[season] = [ep.get("episode_number") for ep in episodes]
|
||||||
|
season_info[season] = episodes
|
||||||
|
# 当前季第一季时间
|
||||||
|
first_date = episodes[0].get("air_date")
|
||||||
|
if re.match(r"^\d{4}-\d{2}-\d{2}$", first_date):
|
||||||
|
season_years[season] = str(first_date).split("-")[0]
|
||||||
|
# 每季集清单
|
||||||
|
if seasons:
|
||||||
|
mediainfo.seasons = seasons
|
||||||
|
mediainfo.number_of_seasons = len(seasons)
|
||||||
|
# 每季集详情
|
||||||
|
if season_info:
|
||||||
|
mediainfo.season_info = season_info
|
||||||
|
# 每季年份
|
||||||
|
if season_years:
|
||||||
|
mediainfo.season_years = season_years
|
||||||
|
# 所有剧集组
|
||||||
|
mediainfo.episode_group = episode_group
|
||||||
|
mediainfo.episode_groups = group_episodes
|
||||||
|
else:
|
||||||
|
# 每季年份
|
||||||
|
season_years = {}
|
||||||
|
for group in mediainfo.episode_groups:
|
||||||
|
if group.get('type') != 6:
|
||||||
|
# 只处理剧集部分
|
||||||
|
continue
|
||||||
|
group_episodes = self.tmdb.get_tv_group_episodes(group.get('id'))
|
||||||
|
if not group_episodes:
|
||||||
|
continue
|
||||||
|
for group_episode in group_episodes:
|
||||||
|
season = group_episode.get('order')
|
||||||
|
episodes = group_episode.get('episodes')
|
||||||
|
if not episodes:
|
||||||
|
continue
|
||||||
|
# 当前季第一季时间
|
||||||
|
first_date = episodes[0].get("air_date")
|
||||||
|
# 判断是不是日期格式
|
||||||
|
if re.match(r"^\d{4}-\d{2}-\d{2}$", first_date):
|
||||||
|
season_years[season] = str(first_date).split("-")[0]
|
||||||
|
if season_years:
|
||||||
|
mediainfo.season_years = season_years
|
||||||
return mediainfo
|
return mediainfo
|
||||||
else:
|
else:
|
||||||
logger.info(f"{meta.name if meta else tmdbid} 未匹配到TMDB媒体信息")
|
logger.info(f"{meta.name if meta else tmdbid} 未匹配到TMDB媒体信息")
|
||||||
@@ -431,13 +491,17 @@ class TheMovieDbModule(_ModuleBase):
|
|||||||
return [schemas.TmdbSeason(**season)
|
return [schemas.TmdbSeason(**season)
|
||||||
for season in tmdb_info.get("seasons", []) if season.get("season_number")]
|
for season in tmdb_info.get("seasons", []) if season.get("season_number")]
|
||||||
|
|
||||||
def tmdb_episodes(self, tmdbid: int, season: int) -> List[schemas.TmdbEpisode]:
|
def tmdb_episodes(self, tmdbid: int, season: int, episode_group: Optional[str] = None) -> List[schemas.TmdbEpisode]:
|
||||||
"""
|
"""
|
||||||
根据TMDBID查询某季的所有信信息
|
根据TMDBID查询某季的所有信信息
|
||||||
:param tmdbid: TMDBID
|
:param tmdbid: TMDBID
|
||||||
:param season: 季
|
:param season: 季
|
||||||
|
:param episode_group: 剧集组
|
||||||
"""
|
"""
|
||||||
season_info = self.tmdb.get_tv_season_detail(tmdbid=tmdbid, season=season)
|
if episode_group:
|
||||||
|
season_info = self.tmdb.get_tv_group_episodes(episode_group)
|
||||||
|
else:
|
||||||
|
season_info = self.tmdb.get_tv_season_detail(tmdbid=tmdbid, season=season)
|
||||||
if not season_info or not season_info.get("episodes"):
|
if not season_info or not season_info.get("episodes"):
|
||||||
return []
|
return []
|
||||||
return [schemas.TmdbEpisode(**episode) for episode in season_info.get("episodes")]
|
return [schemas.TmdbEpisode(**episode) for episode in season_info.get("episodes")]
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ class TmdbScraper:
|
|||||||
else:
|
else:
|
||||||
if season is not None:
|
if season is not None:
|
||||||
# 查询季信息
|
# 查询季信息
|
||||||
seasoninfo = self.tmdb.get_tv_season_detail(mediainfo.tmdb_id, season)
|
if mediainfo.episode_group:
|
||||||
|
seasoninfo = self.tmdb.get_tv_group_episodes(mediainfo.episode_group)
|
||||||
|
else:
|
||||||
|
seasoninfo = self.tmdb.get_tv_season_detail(mediainfo.tmdb_id, season)
|
||||||
if episode:
|
if episode:
|
||||||
# 集元数据文件
|
# 集元数据文件
|
||||||
episodeinfo = self.__get_episode_detail(seasoninfo, meta.begin_episode)
|
episodeinfo = self.__get_episode_detail(seasoninfo, meta.begin_episode)
|
||||||
@@ -61,7 +64,10 @@ class TmdbScraper:
|
|||||||
# 只需要集的图片
|
# 只需要集的图片
|
||||||
if episode:
|
if episode:
|
||||||
# 集的图片
|
# 集的图片
|
||||||
seasoninfo = self.tmdb.get_tv_season_detail(mediainfo.tmdb_id, season)
|
if mediainfo.episode_group:
|
||||||
|
seasoninfo = self.tmdb.get_tv_group_episodes(mediainfo.episode_group)
|
||||||
|
else:
|
||||||
|
seasoninfo = self.tmdb.get_tv_season_detail(mediainfo.tmdb_id, season)
|
||||||
if seasoninfo:
|
if seasoninfo:
|
||||||
episodeinfo = self.__get_episode_detail(seasoninfo, episode)
|
episodeinfo = self.__get_episode_detail(seasoninfo, episode)
|
||||||
if episodeinfo and episodeinfo.get("still_path"):
|
if episodeinfo and episodeinfo.get("still_path"):
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
@@ -187,7 +188,8 @@ class TmdbApi:
|
|||||||
mtype: MediaType,
|
mtype: MediaType,
|
||||||
year: Optional[str] = None,
|
year: Optional[str] = None,
|
||||||
season_year: Optional[str] = None,
|
season_year: Optional[str] = None,
|
||||||
season_number: Optional[int] = None) -> Optional[dict]:
|
season_number: Optional[int] = None,
|
||||||
|
group_episodes: Optional[List[dict]] = None) -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
搜索tmdb中的媒体信息,匹配返回一条尽可能正确的信息
|
搜索tmdb中的媒体信息,匹配返回一条尽可能正确的信息
|
||||||
:param name: 检索的名称
|
:param name: 检索的名称
|
||||||
@@ -195,6 +197,7 @@ class TmdbApi:
|
|||||||
:param year: 年份,如要是季集需要是首播年份(first_air_date)
|
:param year: 年份,如要是季集需要是首播年份(first_air_date)
|
||||||
:param season_year: 当前季集年份
|
:param season_year: 当前季集年份
|
||||||
:param season_number: 季集,整数
|
:param season_number: 季集,整数
|
||||||
|
:param group_episodes: 集数组信息
|
||||||
:return: TMDB的INFO,同时会将mtype赋值到media_type中
|
:return: TMDB的INFO,同时会将mtype赋值到media_type中
|
||||||
"""
|
"""
|
||||||
if not self.search:
|
if not self.search:
|
||||||
@@ -222,7 +225,8 @@ class TmdbApi:
|
|||||||
f"正在识别{mtype.value}:{name}, 季集={season_number}, 季集年份={season_year} ...")
|
f"正在识别{mtype.value}:{name}, 季集={season_number}, 季集年份={season_year} ...")
|
||||||
info = self.__search_tv_by_season(name,
|
info = self.__search_tv_by_season(name,
|
||||||
season_year,
|
season_year,
|
||||||
season_number)
|
season_number,
|
||||||
|
group_episodes)
|
||||||
if not info:
|
if not info:
|
||||||
year_range = [year]
|
year_range = [year]
|
||||||
if year:
|
if year:
|
||||||
@@ -332,12 +336,14 @@ class TmdbApi:
|
|||||||
return tv
|
return tv
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def __search_tv_by_season(self, name: str, season_year: str, season_number: int) -> Optional[dict]:
|
def __search_tv_by_season(self, name: str, season_year: str, season_number: int,
|
||||||
|
group_episodes: Optional[List[dict]] = None) -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
根据电视剧的名称和季的年份及序号匹配TMDB
|
根据电视剧的名称和季的年份及序号匹配TMDB
|
||||||
:param name: 识别的文件名或者种子名
|
:param name: 识别的文件名或者种子名
|
||||||
:param season_year: 季的年份
|
:param season_year: 季的年份
|
||||||
:param season_number: 季序号
|
:param season_number: 季序号
|
||||||
|
:param group_episodes: 集数组信息
|
||||||
:return: 匹配的媒体信息
|
:return: 匹配的媒体信息
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -345,12 +351,25 @@ class TmdbApi:
|
|||||||
if not tv_info:
|
if not tv_info:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
seasons = self.__get_tv_seasons(tv_info)
|
if group_episodes:
|
||||||
for season, season_info in seasons.items():
|
for group_episode in group_episodes:
|
||||||
if season_info.get("air_date"):
|
season = group_episode.get('order')
|
||||||
if season_info.get("air_date")[0:4] == str(_season_year) \
|
if season != season_number:
|
||||||
and season == int(season_number):
|
continue
|
||||||
return True
|
episodes = group_episode.get('episodes')
|
||||||
|
if not episodes:
|
||||||
|
continue
|
||||||
|
first_date = episodes[0].get("air_date")
|
||||||
|
if re.match(r"^\d{4}-\d{2}-\d{2}$", first_date):
|
||||||
|
if str(_season_year) == str(first_date).split("-")[0]:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
seasons = self.__get_tv_seasons(tv_info)
|
||||||
|
for season, season_info in seasons.items():
|
||||||
|
if season_info.get("air_date"):
|
||||||
|
if season_info.get("air_date")[0:4] == str(_season_year) \
|
||||||
|
and season == int(season_number):
|
||||||
|
return True
|
||||||
except Exception as e1:
|
except Exception as e1:
|
||||||
logger.error(f"连接TMDB出错:{e1}")
|
logger.error(f"连接TMDB出错:{e1}")
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
@@ -1317,6 +1336,19 @@ class TmdbApi:
|
|||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_tv_group_episodes(self, group_id: str) -> List[dict]:
|
||||||
|
"""
|
||||||
|
获取电视剧剧集组集列表
|
||||||
|
"""
|
||||||
|
if not self.tv:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
logger.debug(f"正在获取剧集组:{group_id}...")
|
||||||
|
return self.tv.group_episodes(group_id) or []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
return []
|
||||||
|
|
||||||
def get_person_detail(self, person_id: int) -> dict:
|
def get_person_detail(self, person_id: int) -> dict:
|
||||||
"""
|
"""
|
||||||
获取人物详情
|
获取人物详情
|
||||||
@@ -1377,37 +1409,6 @@ class TmdbApi:
|
|||||||
"""
|
"""
|
||||||
self.tmdb.cache_clear()
|
self.tmdb.cache_clear()
|
||||||
|
|
||||||
def get_tv_episode_years(self, episode_groups: List[dict]) -> dict:
|
|
||||||
"""
|
|
||||||
查询剧集组年份
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if not episode_groups:
|
|
||||||
return {}
|
|
||||||
episode_years = {}
|
|
||||||
for episode_group in episode_groups:
|
|
||||||
if episode_group.get('type') != 6:
|
|
||||||
# 只处理剧集部分
|
|
||||||
continue
|
|
||||||
logger.debug(f"正在获取剧集组年份:{episode_group.get('id')}...")
|
|
||||||
group_episodes = self.tv.group_episodes(episode_group.get('id'))
|
|
||||||
if not group_episodes:
|
|
||||||
continue
|
|
||||||
for group_episode in group_episodes:
|
|
||||||
order = group_episode.get('order')
|
|
||||||
episodes = group_episode.get('episodes')
|
|
||||||
if not episodes:
|
|
||||||
continue
|
|
||||||
# 当前季第一季时间
|
|
||||||
first_date = episodes[0].get("air_date")
|
|
||||||
if not first_date and str(first_date).split("-") != 3:
|
|
||||||
continue
|
|
||||||
episode_years[order] = str(first_date).split("-")[0]
|
|
||||||
return episode_years
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(str(e))
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
关闭连接
|
关闭连接
|
||||||
|
|||||||
@@ -170,8 +170,10 @@ class MediaInfo(BaseModel):
|
|||||||
runtime: Optional[int] = None
|
runtime: Optional[int] = None
|
||||||
# 下一集
|
# 下一集
|
||||||
next_episode_to_air: Optional[dict] = Field(default_factory=dict)
|
next_episode_to_air: Optional[dict] = Field(default_factory=dict)
|
||||||
# 剧集组
|
# 全部剧集组
|
||||||
episode_groups: Optional[list] = Field(default_factory=list)
|
episode_groups: Optional[list] = Field(default_factory=list)
|
||||||
|
# 剧集组
|
||||||
|
episode_group: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class TorrentInfo(BaseModel):
|
class TorrentInfo(BaseModel):
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ class DownloadHistory(BaseModel):
|
|||||||
note: Optional[Any] = None
|
note: Optional[Any] = None
|
||||||
# 自定义媒体类别
|
# 自定义媒体类别
|
||||||
media_category: Optional[str] = None
|
media_category: Optional[str] = None
|
||||||
|
# 自定义剧集组
|
||||||
|
episode_group: Optional[str] = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
@@ -86,6 +88,8 @@ class TransferHistory(BaseModel):
|
|||||||
image: Optional[str] = None
|
image: Optional[str] = None
|
||||||
# 下载器Hash
|
# 下载器Hash
|
||||||
download_hash: Optional[str] = None
|
download_hash: Optional[str] = None
|
||||||
|
# 自定义剧集组
|
||||||
|
episode_group: Optional[str] = None
|
||||||
# 状态 1-成功,0-失败
|
# 状态 1-成功,0-失败
|
||||||
status: bool = True
|
status: bool = True
|
||||||
# 失败原因
|
# 失败原因
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ class Subscribe(BaseModel):
|
|||||||
media_category: Optional[str] = None
|
media_category: Optional[str] = None
|
||||||
# 过滤规则组
|
# 过滤规则组
|
||||||
filter_groups: Optional[List[str]] = Field(default_factory=list)
|
filter_groups: Optional[List[str]] = Field(default_factory=list)
|
||||||
|
# 可选剧集组
|
||||||
|
episode_groups: Optional[list] = Field(default_factory=list)
|
||||||
|
# 剧集组
|
||||||
|
episode_group: str = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
@@ -130,6 +134,8 @@ class SubscribeShare(BaseModel):
|
|||||||
custom_words: Optional[str] = None
|
custom_words: Optional[str] = None
|
||||||
# 自定义媒体类别
|
# 自定义媒体类别
|
||||||
media_category: Optional[str] = None
|
media_category: Optional[str] = None
|
||||||
|
# 自定义剧集组
|
||||||
|
episode_group: Optional[str] = None
|
||||||
# 复用人次
|
# 复用人次
|
||||||
count: Optional[int] = 0
|
count: Optional[int] = 0
|
||||||
|
|
||||||
|
|||||||
@@ -200,3 +200,5 @@ class ManualTransferItem(BaseModel):
|
|||||||
library_category_folder: Optional[bool] = None
|
library_category_folder: Optional[bool] = None
|
||||||
# 复用历史识别信息
|
# 复用历史识别信息
|
||||||
from_history: Optional[bool] = False
|
from_history: Optional[bool] = False
|
||||||
|
# 剧集组
|
||||||
|
episode_group: Optional[str] = None
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""2.1.3
|
||||||
|
|
||||||
|
Revision ID: 4b544f5d3b07
|
||||||
|
Revises: 610bb05ddeef
|
||||||
|
Create Date: 2025-04-03 11:21:42.780337
|
||||||
|
|
||||||
|
"""
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import sqlite
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4b544f5d3b07'
|
||||||
|
down_revision = '610bb05ddeef'
|
||||||
|
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('episode_group', sa.String, nullable=True))
|
||||||
|
op.add_column('subscribe', sa.Column('episode_groups', sa.JSON(), nullable=True))
|
||||||
|
op.add_column('subscribe', sa.Column('episode_group', sa.String, nullable=True))
|
||||||
|
op.add_column('subscribehistory', sa.Column('episode_group', sa.String, nullable=True))
|
||||||
|
op.add_column('transferhistory', sa.Column('episode_group', sa.String, nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user