From bd9d1c413fae34fb8a60a0ee42c0e6b38cef6803 Mon Sep 17 00:00:00 2001 From: Pixel-LH <2569646547@qq.com> Date: Mon, 2 Sep 2024 00:00:45 +0800 Subject: [PATCH] =?UTF-8?q?Update:VCB=E8=BE=85=E5=8A=A9=E6=95=B4=E7=90=86?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=8F=90=E9=AB=98=E8=AF=86=E5=88=AB=E7=8E=87?= =?UTF-8?q?&Fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- plugins/vcbanimemonitor/__init__.py | 65 ++++++----------- plugins/vcbanimemonitor/remeta.py | 109 +++++++++++++++------------- 3 files changed, 83 insertions(+), 94 deletions(-) diff --git a/package.json b/package.json index f2db2d8..de920a7 100644 --- a/package.json +++ b/package.json @@ -317,13 +317,14 @@ }, "VCBAnimeMonitor": { "name": "整理VCB动漫压制组作品", - "description": "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理", + "description": "一款辅助整理&提高识别VCB-Stuido动漫压制组作品的插件", "labels": "文件整理,识别", "version": "1.8", "icon": "vcbmonitor.png", "author": "pixel@qingwa", "level": 2, "history": { + "v1.8.2": "提高识别率", "v1.8.1": "重构插件,测试版", "v1.8": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削", "v1.7.1": "修复偶尔安装失败问题" diff --git a/plugins/vcbanimemonitor/__init__.py b/plugins/vcbanimemonitor/__init__.py index 84c5f86..97ae16c 100644 --- a/plugins/vcbanimemonitor/__init__.py +++ b/plugins/vcbanimemonitor/__init__.py @@ -5,6 +5,7 @@ import threading import time import traceback from pathlib import Path +from time import sleep from typing import List, Tuple, Dict, Any, Optional import pytz import qbittorrentapi @@ -70,11 +71,11 @@ class VCBAnimeMonitor(_PluginBase): # 插件名称 plugin_name = "整理VCB动漫压制组作品" # 插件描述 - plugin_desc = "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理" + plugin_desc = "一款辅助整理&提高识别VCB-Stuido动漫压制组作品的插件" # 插件图标 plugin_icon = "vcbmonitor.png" # 插件版本 - plugin_version = "1.8.1" + plugin_version = "1.8.2" # 插件作者 plugin_author = "pixel@qingwa" # 作者主页 @@ -88,7 +89,6 @@ class VCBAnimeMonitor(_PluginBase): # 私有属性 _switch_ova = False - _high_mode = False _torrents_path = None new_save_path = None qb = None @@ -142,7 +142,6 @@ class VCBAnimeMonitor(_PluginBase): self._size = config.get("size") or 0 self._scrape = config.get("scrape") self._switch_ova = config.get("ova") - self._high_mode = config.get("high_mode") self._torrents_path = config.get("torrents_path") or "" # 停止现有任务 @@ -161,13 +160,16 @@ class VCBAnimeMonitor(_PluginBase): return # 启用种子目录监控 - if self._torrents_path is not None and Path(self._torrents_path).exists() and self._enabled: + if self._torrents_path and Path(self._torrents_path).exists() and self._enabled: # 只取第一个目录作为新的保存 - first_path = monitor_dirs[0] - if SystemUtils.is_windows(): - self.new_save_path = first_path.split(':')[0] + ":" + first_path.split(':')[1] - else: - self.new_save_path = first_path.split(':')[0] + try: + first_path = monitor_dirs[0] + if SystemUtils.is_windows(): + self.new_save_path = first_path.split(':')[0] + ":" + first_path.split(':')[1] + else: + self.new_save_path = first_path.split(':')[0] + except Exception: + logger.error(f"目录保存失败,请检查输入目录是否合法") # print(self.new_save_path) try: observer = Observer() @@ -288,7 +290,6 @@ class VCBAnimeMonitor(_PluginBase): "size": self._size, "scrape": self._scrape, "ova": self._switch_ova, - "high_mode": self._high_mode, "torrents_path": self._torrents_path }) @@ -388,7 +389,7 @@ class VCBAnimeMonitor(_PluginBase): logger.warn("不属于VCB的作品,不处理!") return - remeta = ReMeta(ova_switch=self._switch_ova, ) + remeta = ReMeta(ova_switch=self._switch_ova,) file_meta = remeta.handel_file(file_path=file_path) if file_meta: if not file_meta.name: @@ -397,15 +398,6 @@ class VCBAnimeMonitor(_PluginBase): if remeta.is_ova and not self._switch_ova: logger.warn(f"{file_path.name} 为OVA资源,未开启OVA开关,不处理") return - # if remeta.is_ova and self._switch_ova: - # logger.info(f"{file_path.name} 为OVA资源,开始处理") - # if self.get_data(key=f"OVA_{file_meta.title}") is not None: - # ova_history_ep = int(self.get_data(key=f"OVA_{file_meta.title}")) + 1 - # file_meta.begin_episode = ova_history_ep - # self.save_data(key=f"OVA_{file_meta.title}", value=ova_history_ep) - # else: - # file_meta.begin_episode = 1 - # self.save_data(key=f"OVA_{file_meta.title}", value=1) if remeta.is_ova and self._switch_ova: logger.info(f"{file_path.name} 为OVA资源,开始历史记录处理") ova_history_ep_list = self.plugindata.get(file_meta.title, []) @@ -833,22 +825,6 @@ class VCBAnimeMonitor(_PluginBase): } ] }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 4 - }, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'high_mode', - 'label': '高性能处理模式', - } - } - ] - }, { 'component': 'VCol', 'props': { @@ -1003,7 +979,7 @@ class VCBAnimeMonitor(_PluginBase): 'props': { 'model': 'monitor_dirs', 'label': '监控目录', - 'rows': 5, + 'rows': 4, 'placeholder': '每一行一个目录,支持以下几种配置方式,转移方式支持 move、copy、link、softlink、rclone_copy、rclone_move:\n' '监控目录\n' '监控目录#转移方式\n' @@ -1051,8 +1027,10 @@ class VCBAnimeMonitor(_PluginBase): 'props': { 'type': 'info', 'variant': 'tonal', - 'text': '核心用法与目录同步插件相同,不同点在于只识别处理VCB-Studio资源,\n' - '不处理SPs目录下的文件,OVA/OAD集数根据入库顺序累加命名,不保证与TMDB集数匹配' + 'text': '核心用法与目录同步插件相同,不同点在于只识别处理VCB-Studio资源。' + '默认不处理SPs、CDs、SCans目录下的文件,OVA/OAD集数暂时根据入库顺序累加命名,' + '因此不保证与TMDB集数匹配。部分季度以罗马音音译为名的作品暂时无法识别出准确季度。' + '有想法,有问题欢迎点击插件作者主页提issue!' } } ] @@ -1073,9 +1051,9 @@ class VCBAnimeMonitor(_PluginBase): 'props': { 'type': 'info', 'variant': 'tonal', - 'text': '最佳使用方式:监控目录单独设置一个作为保存VCB-Studio资源的目录,\n' - '填入监控种子目录,开启后会将正在QB(仅支持QB)下载器内的VCB-Studio资源转移到监控目录实现自动整理(' - '仅支持第一个监控目录),\n' + 'text': '最佳使用方式:监控目录单独设置一个作为保存VCB-Studio资源的目录,' + '填入监控种子目录,开启后会将正在QB(仅支持QB)下载器内正在下载的VCB-Studio资源转移到监控目录实现自动整理(' + '仅支持第一个监控目录),' '监控种子目录为空则不转移文件' } } @@ -1097,7 +1075,6 @@ class VCBAnimeMonitor(_PluginBase): "cron": "", "size": 0, "ova": False, - "high_mode": False, "torrents_path": "", } diff --git a/plugins/vcbanimemonitor/remeta.py b/plugins/vcbanimemonitor/remeta.py index 5b8a659..260e17f 100644 --- a/plugins/vcbanimemonitor/remeta.py +++ b/plugins/vcbanimemonitor/remeta.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from pathlib import Path from typing import List from app.chain.media import MediaChain +from app.chain.tmdb import TmdbChain from app.core.metainfo import MetaInfoPath from app.log import logger from app.schemas import MediaType @@ -33,6 +34,11 @@ final_season_patterns = [ re.compile(r'\sFinal') ] +movie_patterns = [ + re.compile("Movie", re.IGNORECASE), + re.compile("the Movie", re.IGNORECASE), +] + @dataclass class VCBMetaBase: @@ -52,6 +58,8 @@ class VCBMetaBase: ep: int = None # 是否是OVA/OAD is_ova: bool = False + # TMDB ID + tmdb_id: int = None blocked_words = ["vcb-studio", "360p", "480p", "720p", "1080p", "2160p", "hdr", "x265", "x264", "aac", "flac"] @@ -83,7 +91,8 @@ class ReMeta: self.vcb_meta.original_title = file_name if not self.is_tv(file_name): logger.warn( - "不符合VCB-Studio的剧集命名规范,归类为电影,跳过剧集模块处理。注意:年份较为久远的作品可能会判断错误") + "不符合VCB-Studio的剧集命名规范,归类为电影,跳过剧集模块处理。注意:年份较为久远的作品可能在此会判断错误") + self.parse_movie() else: self.tv_mode() self.is_ova = self.vcb_meta.is_ova @@ -95,6 +104,8 @@ class ReMeta: meta.begin_episode = self.vcb_meta.ep if self.vcb_meta.type == "Movie": meta.type = MediaType.MOVIE + else: + meta.type = MediaType.TV return meta def split_season_ep(self): @@ -179,7 +190,8 @@ class ReMeta: self.vcb_meta.is_ova = True # 直接获取数字 self.vcb_meta.ep = int(re.search(r"\d+", ep).group()) or 1 - logger.info(f"识别出集数为{self.vcb_meta.ep}") + logger.info(f"OVA模式下识别出集数为{self.vcb_meta.ep}") + self.vcb_meta.season = 0 return def culling_blocked_words(self): @@ -192,42 +204,53 @@ class ReMeta: def handle_final_season(self): - meta, medias = MediaChain().search(title=self.vcb_meta.title) + _, medias = MediaChain().search(title=self.vcb_meta.title) if not medias: logger.warning("匹配到最终季时无法找到对应的媒体信息!季度返回默认值:1") self.vcb_meta.season = 1 return - max_season_number = 1 - # 当没有季度参考时用评分来决定 - vote_average = 0 - season_info = False - for media in medias: - if media.type != MediaType.TV: - logger.info(f"搜索到的: {media.title}, 媒体类型为 {media.type},跳过") - continue - if media.season_info: - season_info = True - last_season_number = int(media.season_info[-1].get("season_number", 1)) - if last_season_number > max_season_number: - max_season_number = last_season_number - else: - logger.info(f"媒体: {media.title} 没有季信息,跳过") - if not season_info: - # 备用方案 - for media in medias: - if media.seasons: - seasons: dict - # 获取最大的键,即最大季度 - last_season_number = max(media.seasons.keys()) - if last_season_number > max_season_number: - max_season_number = last_season_number - logger.info(f"获取到最终季,季度为 {max_season_number},标题为 {media.title},年份为 {media.year}") - else: - logger.info(f"媒体: {media.title} 没有季信息,跳过") + filter_medias = [media for media in medias if media.type == MediaType.TV] + if not filter_medias: + logger.warning("匹配到最终季时无法找到对应的媒体信息!季度返回默认值:1") + self.vcb_meta.season = 1 + return + medias = [media for media in filter_medias if media.popularity or media.vote_average] + if not medias: + logger.warning("匹配到最终季时无法找到对应的媒体信息!季度返回默认值:1") + self.vcb_meta.season = 1 + return + # 获取欢迎度最高或者评分最高的媒体 + medias_sorted = sorted(medias, key=lambda x: x.popularity or x.vote_average, reverse=True)[0] + self.vcb_meta.tmdb_id = medias_sorted.tmdb_id + if medias_sorted.tmdb_id: + seasons_info = TmdbChain().tmdb_seasons(tmdbid=medias_sorted.tmdb_id) + if seasons_info: + self.vcb_meta.season = len(seasons_info) + logger.info(f"获取到最终季度,季度为{self.vcb_meta.season}") + return + logger.warning("无法获取到最终季度信息,季度返回默认值:1") + self.vcb_meta.season = 1 + + + + def parse_movie(self): + logger.info("开始尝试剧场版模式解析") + for pattern in movie_patterns: + if pattern.search(self.vcb_meta.title): + logger.info("命中剧场版匹配规则,加上剧场版标识辅助识别") + self.vcb_meta.type = "Movie" + self.vcb_meta.title = pattern.sub("", self.vcb_meta.title).strip() + self.vcb_meta.title = self.vcb_meta.title + return + + def find_ova_episode(self): + """ + 搜索OVA的集数 + TODO:模糊匹配OVA的集数 + """ + pass - self.vcb_meta.season = max_season_number - logger.info(f"获取到最终季,季度为 {self.vcb_meta.season}") @staticmethod def roman_to_int(s) -> int: @@ -250,21 +273,9 @@ class ReMeta: return total -def test(title: str): - # 示例文件名 - pre_title = title - # 提取方括号内的内容,不包括方括号 - content = re.findall(r'\[(.*?)\]', pre_title) - - print(content) - - -if __name__ == '__main__': - # title = "[BeanSub&VCB-Studio] Jujutsu Kaisen [26][Ma10p_1080p][x265_flac].mkv " - # test(title) - - ReMeta( - ova_switch=True, - ).handel_file(Path( - r"[Nekomoe kissaten&VCB-Studio] Fruits Basket The Final [08][Ma10p_1080p][x265_flac].mkv")) +# if __name__ == '__main__': +# ReMeta( +# ova_switch=True, +# ).handel_file(Path( +# r"[Airota&Nekomoe kissaten&VCB-Studio] Yuru Camp [Heya Camp EP00][Ma10p_1080p][x265_flac].mkv"))