diff --git a/app/db/models/downloadhistory.py b/app/db/models/downloadhistory.py index dc3dbd42..f624d09d 100644 --- a/app/db/models/downloadhistory.py +++ b/app/db/models/downloadhistory.py @@ -52,7 +52,7 @@ class DownloadHistory(Base): @staticmethod 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): """ 据tmdbid、season、season_episode查询转移记录 """ diff --git a/app/db/models/transferhistory.py b/app/db/models/transferhistory.py index dd8eb392..9f1686ef 100644 --- a/app/db/models/transferhistory.py +++ b/app/db/models/transferhistory.py @@ -86,7 +86,7 @@ class TransferHistory(Base): @staticmethod 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): """ 据tmdbid、season、season_episode查询转移记录 """ diff --git a/app/db/transferhistory_oper.py b/app/db/transferhistory_oper.py index 3c5e6688..c9f9c395 100644 --- a/app/db/transferhistory_oper.py +++ b/app/db/transferhistory_oper.py @@ -52,7 +52,7 @@ class TransferHistoryOper(DbOper): return TransferHistory.statistic(self._db, days) 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]: """ 按类型、标题、年份、季集查询转移记录 """ diff --git a/app/modules/themoviedb/__init__.py b/app/modules/themoviedb/__init__.py index 70685dad..4f85b539 100644 --- a/app/modules/themoviedb/__init__.py +++ b/app/modules/themoviedb/__init__.py @@ -194,12 +194,17 @@ class TheMovieDbModule(_ModuleBase): scrape_path = path / path.name self.scraper.gen_scraper_files(mediainfo=mediainfo, file_path=scrape_path) + elif path.is_file(): + # 单个文件 + logger.info(f"开始刮削媒体库文件:{path} ...") + self.scraper.gen_scraper_files(mediainfo=mediainfo, + file_path=path) else: # 目录下的所有文件 + logger.info(f"开始刮削目录:{path} ...") for file in SystemUtils.list_files(path, settings.RMT_MEDIAEXT): if not file: continue - logger.info(f"开始刮削媒体库文件:{file} ...") self.scraper.gen_scraper_files(mediainfo=mediainfo, file_path=file) logger.info(f"{path} 刮削完成") diff --git a/app/plugins/libraryscraper/__init__.py b/app/plugins/libraryscraper/__init__.py index b55fa6ee..38fc0cff 100644 --- a/app/plugins/libraryscraper/__init__.py +++ b/app/plugins/libraryscraper/__init__.py @@ -47,6 +47,7 @@ class LibraryScraper(_PluginBase): _enabled = False _onlyonce = False _cron = None + _mode = "" _scraper_paths = "" _exclude_paths = "" # 退出事件 @@ -58,6 +59,7 @@ class LibraryScraper(_PluginBase): self._enabled = config.get("enabled") self._onlyonce = config.get("onlyonce") self._cron = config.get("cron") + self._mode = config.get("mode") or "" self._scraper_paths = config.get("scraper_paths") or "" self._exclude_paths = config.get("exclude_paths") or "" @@ -92,6 +94,7 @@ class LibraryScraper(_PluginBase): "onlyonce": False, "enabled": self._enabled, "cron": self._cron, + "mode": self._mode, "scraper_paths": self._scraper_paths, "exclude_paths": self._exclude_paths }) @@ -155,6 +158,28 @@ class LibraryScraper(_PluginBase): { 'component': 'VRow', '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', 'props': { @@ -189,7 +214,7 @@ class LibraryScraper(_PluginBase): 'model': 'scraper_paths', 'label': '削刮路径', 'rows': 5, - 'placeholder': '每一行一个目录' + 'placeholder': '每一行一个目录,需配置到媒体文件的上级目录,即开了二级分类时需要配置到二级分类目录' } } ] @@ -223,6 +248,7 @@ class LibraryScraper(_PluginBase): ], { "enabled": False, "cron": "0 0 */7 * *", + "mode": "", "scraper_paths": "", "err_hosts": "" } @@ -236,49 +262,77 @@ class LibraryScraper(_PluginBase): """ if not self._scraper_paths: return + # 排除目录 + exclude_paths = self._exclude_paths.split("\n") # 已选择的目录 paths = self._scraper_paths.split("\n") for path in paths: if not path: continue - if not Path(path).exists(): + scraper_path = Path(path) + if not scraper_path.exists(): logger.warning(f"媒体库刮削路径不存在:{path}") continue logger.info(f"开始刮削媒体库:{path} ...") - if self._event.is_set(): - logger.info(f"媒体库刮削服务停止") - return - # 刮削目录 - self.__scrape_dir(Path(path)) - logger.info(f"媒体库刮削完成") + # 遍历一层文件夹 + for sub_path in scraper_path.iterdir(): + if self._event.is_set(): + logger.info(f"媒体库刮削服务停止") + return + # 排除目录 + 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): """ - 削刮一个目录 + 削刮一个目录,该目录必须是媒体文件目录 """ - 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) for file in files: if self._event.is_set(): logger.info(f"媒体库刮削服务停止") 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) - if meta_info.type == MediaType.TV: - dir_info = MetaInfo(file.parent.parent.name) - else: - dir_info = MetaInfo(file.parent.name) - meta_info.merge(dir_info) + # 合并 + meta_info.merge(dir_meta) # 优先读取本地nfo文件 tmdbid = None if meta_info.type == MediaType.MOVIE: diff --git a/app/plugins/mediasyncdel/__init__.py b/app/plugins/mediasyncdel/__init__.py index 61ef4513..668ec25f 100644 --- a/app/plugins/mediasyncdel/__init__.py +++ b/app/plugins/mediasyncdel/__init__.py @@ -418,7 +418,7 @@ class MediaSyncDel(_PluginBase): ] @eventmanager.register(EventType.WebhookMessage) - def sync_del_by_webhook(self, event): + def sync_del_by_webhook(self, event: Event): """ emby删除媒体库同步删除历史记录 webhook @@ -507,7 +507,8 @@ class MediaSyncDel(_PluginBase): season_num=season_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} 已被排除,暂不处理") 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, media_name=media_name, @@ -632,10 +626,19 @@ class MediaSyncDel(_PluginBase): # 保存历史 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": msg = f'电影 {media_name} {tmdb_id}' @@ -1061,7 +1064,7 @@ class MediaSyncDel(_PluginBase): return del_medias @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_res = Jellyfin().get_data(log_url) @@ -1134,7 +1137,7 @@ class MediaSyncDel(_PluginBase): return del_medias @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")) @staticmethod - def get_tmdbimage_url(path, prefix="w500"): + def get_tmdbimage_url(path: str, prefix="w500"): if not path: return "" tmdb_image_url = f"https://{settings.TMDB_IMAGE_DOMAIN}"