feat 媒体库刮削支持覆盖

fix 类型声明
This commit is contained in:
jxxghp 2023-09-07 12:35:35 +08:00
parent b899b23d04
commit 2c61d439ca
6 changed files with 103 additions and 41 deletions

View File

@ -52,7 +52,7 @@ class DownloadHistory(Base):
@staticmethod @staticmethod
def get_last_by(db: Session, mtype: str = None, title: str = None, year: int = None, season: str = None, def get_last_by(db: Session, mtype: str = None, title: str = None, year: int = None, season: str = None,
episode: str = None, tmdbid: str = None): episode: str = None, tmdbid: int = None):
""" """
据tmdbidseasonseason_episode查询转移记录 据tmdbidseasonseason_episode查询转移记录
""" """

View File

@ -86,7 +86,7 @@ class TransferHistory(Base):
@staticmethod @staticmethod
def list_by(db: Session, title: str = None, year: int = None, season: str = None, def list_by(db: Session, title: str = None, year: int = None, season: str = None,
episode: str = None, tmdbid: str = None): episode: str = None, tmdbid: int = None):
""" """
据tmdbidseasonseason_episode查询转移记录 据tmdbidseasonseason_episode查询转移记录
""" """

View File

@ -52,7 +52,7 @@ class TransferHistoryOper(DbOper):
return TransferHistory.statistic(self._db, days) return TransferHistory.statistic(self._db, days)
def get_by(self, title: str = None, year: str = None, def get_by(self, title: str = None, year: str = None,
season: str = None, episode: str = None, tmdbid: str = None) -> List[TransferHistory]: season: str = None, episode: str = None, tmdbid: int = None) -> List[TransferHistory]:
""" """
按类型标题年份季集查询转移记录 按类型标题年份季集查询转移记录
""" """

View File

@ -194,12 +194,17 @@ class TheMovieDbModule(_ModuleBase):
scrape_path = path / path.name scrape_path = path / path.name
self.scraper.gen_scraper_files(mediainfo=mediainfo, self.scraper.gen_scraper_files(mediainfo=mediainfo,
file_path=scrape_path) file_path=scrape_path)
elif path.is_file():
# 单个文件
logger.info(f"开始刮削媒体库文件:{path} ...")
self.scraper.gen_scraper_files(mediainfo=mediainfo,
file_path=path)
else: else:
# 目录下的所有文件 # 目录下的所有文件
logger.info(f"开始刮削目录:{path} ...")
for file in SystemUtils.list_files(path, settings.RMT_MEDIAEXT): for file in SystemUtils.list_files(path, settings.RMT_MEDIAEXT):
if not file: if not file:
continue continue
logger.info(f"开始刮削媒体库文件:{file} ...")
self.scraper.gen_scraper_files(mediainfo=mediainfo, self.scraper.gen_scraper_files(mediainfo=mediainfo,
file_path=file) file_path=file)
logger.info(f"{path} 刮削完成") logger.info(f"{path} 刮削完成")

View File

@ -47,6 +47,7 @@ class LibraryScraper(_PluginBase):
_enabled = False _enabled = False
_onlyonce = False _onlyonce = False
_cron = None _cron = None
_mode = ""
_scraper_paths = "" _scraper_paths = ""
_exclude_paths = "" _exclude_paths = ""
# 退出事件 # 退出事件
@ -58,6 +59,7 @@ class LibraryScraper(_PluginBase):
self._enabled = config.get("enabled") self._enabled = config.get("enabled")
self._onlyonce = config.get("onlyonce") self._onlyonce = config.get("onlyonce")
self._cron = config.get("cron") self._cron = config.get("cron")
self._mode = config.get("mode") or ""
self._scraper_paths = config.get("scraper_paths") or "" self._scraper_paths = config.get("scraper_paths") or ""
self._exclude_paths = config.get("exclude_paths") or "" self._exclude_paths = config.get("exclude_paths") or ""
@ -92,6 +94,7 @@ class LibraryScraper(_PluginBase):
"onlyonce": False, "onlyonce": False,
"enabled": self._enabled, "enabled": self._enabled,
"cron": self._cron, "cron": self._cron,
"mode": self._mode,
"scraper_paths": self._scraper_paths, "scraper_paths": self._scraper_paths,
"exclude_paths": self._exclude_paths "exclude_paths": self._exclude_paths
}) })
@ -155,6 +158,28 @@ class LibraryScraper(_PluginBase):
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSelect',
'props': {
'model': 'mode',
'label': '刮削模式',
'items': [
{'title': '仅刮削缺失元数据和图片', 'value': ''},
{'title': '覆盖所有元数据和图片', 'value': 'force_all'},
{'title': '覆盖所有元数据', 'value': 'force_nfo'},
{'title': '覆盖所有图片', 'value': 'force_image'},
]
}
}
]
},
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
@ -189,7 +214,7 @@ class LibraryScraper(_PluginBase):
'model': 'scraper_paths', 'model': 'scraper_paths',
'label': '削刮路径', 'label': '削刮路径',
'rows': 5, 'rows': 5,
'placeholder': '每一行一个目录' 'placeholder': '每一行一个目录,需配置到媒体文件的上级目录,即开了二级分类时需要配置到二级分类目录'
} }
} }
] ]
@ -223,6 +248,7 @@ class LibraryScraper(_PluginBase):
], { ], {
"enabled": False, "enabled": False,
"cron": "0 0 */7 * *", "cron": "0 0 */7 * *",
"mode": "",
"scraper_paths": "", "scraper_paths": "",
"err_hosts": "" "err_hosts": ""
} }
@ -236,49 +262,77 @@ class LibraryScraper(_PluginBase):
""" """
if not self._scraper_paths: if not self._scraper_paths:
return return
# 排除目录
exclude_paths = self._exclude_paths.split("\n")
# 已选择的目录 # 已选择的目录
paths = self._scraper_paths.split("\n") paths = self._scraper_paths.split("\n")
for path in paths: for path in paths:
if not path: if not path:
continue continue
if not Path(path).exists(): scraper_path = Path(path)
if not scraper_path.exists():
logger.warning(f"媒体库刮削路径不存在:{path}") logger.warning(f"媒体库刮削路径不存在:{path}")
continue continue
logger.info(f"开始刮削媒体库:{path} ...") logger.info(f"开始刮削媒体库:{path} ...")
if self._event.is_set(): # 遍历一层文件夹
logger.info(f"媒体库刮削服务停止") for sub_path in scraper_path.iterdir():
return if self._event.is_set():
# 刮削目录 logger.info(f"媒体库刮削服务停止")
self.__scrape_dir(Path(path)) return
logger.info(f"媒体库刮削完成") # 排除目录
exclude_flag = False
for exclude_path in exclude_paths:
try:
if sub_path.is_relative_to(Path(exclude_path)):
exclude_flag = True
break
except Exception as err:
print(str(err))
if exclude_flag:
logger.debug(f"{sub_path} 在排除目录中,跳过 ...")
continue
# 开始刮削目录
if sub_path.is_dir():
logger.info(f"开始刮削目录:{sub_path} ...")
self.__scrape_dir(sub_path)
logger.info(f"目录 {sub_path} 刮削完成")
logger.info(f"媒体库 {path} 刮削完成")
def __scrape_dir(self, path: Path): def __scrape_dir(self, path: Path):
""" """
削刮一个目录 削刮一个目录该目录必须是媒体文件目录
""" """
exclude_paths = self._exclude_paths.split("\n") # 覆盖模式时提前删除nfo
if self._mode in ["force_all", "force_nfo"]:
nfo_files = SystemUtils.list_files(path, [".nfo"])
for nfo_file in nfo_files:
try:
logger.warn(f"删除nfo文件{nfo_file}")
nfo_file.unlink()
except Exception as err:
print(str(err))
# 覆盖模式时,提前删除图片文件
if self._mode in ["force_all", "force_image"]:
image_files = SystemUtils.list_files(path, [".jpg", ".png"])
for image_file in image_files:
try:
logger.warn(f"删除图片文件:{image_file}")
image_file.unlink()
except Exception as err:
print(str(err))
# 目录识别
dir_meta = MetaInfo(path.name)
# 查找目录下所有的文件 # 查找目录下所有的文件
files = SystemUtils.list_files(path, settings.RMT_MEDIAEXT) files = SystemUtils.list_files(path, settings.RMT_MEDIAEXT)
for file in files: for file in files:
if self._event.is_set(): if self._event.is_set():
logger.info(f"媒体库刮削服务停止") logger.info(f"媒体库刮削服务停止")
return return
# 排除目录
exclude_flag = False
for exclude_path in exclude_paths:
if file.is_relative_to(Path(exclude_path)):
exclude_flag = True
break
if exclude_flag:
logger.debug(f"{file} 在排除目录中,跳过 ...")
continue
# 识别媒体文件 # 识别媒体文件
meta_info = MetaInfo(file.name) meta_info = MetaInfo(file.name)
if meta_info.type == MediaType.TV: # 合并
dir_info = MetaInfo(file.parent.parent.name) meta_info.merge(dir_meta)
else:
dir_info = MetaInfo(file.parent.name)
meta_info.merge(dir_info)
# 优先读取本地nfo文件 # 优先读取本地nfo文件
tmdbid = None tmdbid = None
if meta_info.type == MediaType.MOVIE: if meta_info.type == MediaType.MOVIE:

View File

@ -418,7 +418,7 @@ class MediaSyncDel(_PluginBase):
] ]
@eventmanager.register(EventType.WebhookMessage) @eventmanager.register(EventType.WebhookMessage)
def sync_del_by_webhook(self, event): def sync_del_by_webhook(self, event: Event):
""" """
emby删除媒体库同步删除历史记录 emby删除媒体库同步删除历史记录
webhook webhook
@ -507,7 +507,8 @@ class MediaSyncDel(_PluginBase):
season_num=season_num, season_num=season_num,
episode_num=episode_num) episode_num=episode_num)
def __sync_del(self, media_type, media_name, media_path, tmdb_id, season_num, episode_num): def __sync_del(self, media_type: str, media_name: str, media_path: str,
tmdb_id: int, season_num: int, episode_num: int):
""" """
执行删除逻辑 执行删除逻辑
""" """
@ -524,13 +525,6 @@ class MediaSyncDel(_PluginBase):
logger.info(f"媒体路径 {media_path} 已被排除,暂不处理") logger.info(f"媒体路径 {media_path} 已被排除,暂不处理")
return return
# 季数
if season_num and str(season_num).isdigit() and int(season_num) < 10:
season_num = f'0{season_num}'
# 集数
if episode_num and str(episode_num).isdigit() and int(episode_num) < 10:
episode_num = f'0{episode_num}'
# 查询转移记录 # 查询转移记录
msg, transfer_history = self.__get_transfer_his(media_type=media_type, msg, transfer_history = self.__get_transfer_his(media_type=media_type,
media_name=media_name, media_name=media_name,
@ -632,10 +626,19 @@ class MediaSyncDel(_PluginBase):
# 保存历史 # 保存历史
self.save_data("history", history) self.save_data("history", history)
def __get_transfer_his(self, media_type, media_name, tmdb_id, season_num, episode_num): def __get_transfer_his(self, media_type: str, media_name: str,
tmdb_id: int, season_num: int, episode_num: int):
""" """
查询转移记录 查询转移记录
""" """
# 季数
if season_num:
season_num = str(season_num).rjust(2, '0')
# 集数
if episode_num:
episode_num = str(episode_num).rjust(2, '0')
# 删除电影 # 删除电影
if media_type == "Movie" or media_type == "MOV": if media_type == "Movie" or media_type == "MOV":
msg = f'电影 {media_name} {tmdb_id}' msg = f'电影 {media_name} {tmdb_id}'
@ -1061,7 +1064,7 @@ class MediaSyncDel(_PluginBase):
return del_medias return del_medias
@staticmethod @staticmethod
def parse_jellyfin_log(last_time): def parse_jellyfin_log(last_time: datetime):
# 根据加入日期 降序排序 # 根据加入日期 降序排序
log_url = "{HOST}System/Logs/Log?name=log_%s.log&api_key={APIKEY}" % datetime.date.today().strftime("%Y%m%d") log_url = "{HOST}System/Logs/Log?name=log_%s.log&api_key={APIKEY}" % datetime.date.today().strftime("%Y%m%d")
log_res = Jellyfin().get_data(log_url) log_res = Jellyfin().get_data(log_url)
@ -1134,7 +1137,7 @@ class MediaSyncDel(_PluginBase):
return del_medias return del_medias
@staticmethod @staticmethod
def delete_media_file(filedir, filename): def delete_media_file(filedir: str, filename: str):
""" """
删除媒体文件空目录也会被删除 删除媒体文件空目录也会被删除
""" """
@ -1202,7 +1205,7 @@ class MediaSyncDel(_PluginBase):
title="媒体库同步删除完成!", userid=event.event_data.get("user")) title="媒体库同步删除完成!", userid=event.event_data.get("user"))
@staticmethod @staticmethod
def get_tmdbimage_url(path, prefix="w500"): def get_tmdbimage_url(path: str, prefix="w500"):
if not path: if not path:
return "" return ""
tmdb_image_url = f"https://{settings.TMDB_IMAGE_DOMAIN}" tmdb_image_url = f"https://{settings.TMDB_IMAGE_DOMAIN}"